Skip to main content

Message Library (Rich Message / Template / Quick Reply)

Overview

This domain stores the message content that goes out over LINE, kept separate from the act of sending, which belongs to the campaign side. That separation is what lets a single piece of content be reused by both campaigns and auto-responses.

Three tables make up the library:

  • rich_message — the primary message, covering text, images, tappable images and flex messages
  • template_message — pre-built flex templates that users fill content into
  • quick_reply — sets of quick-reply buttons that can be attached to a message

Core Data Structure

rich_message (model RichMessage)

Holds title and content JSONB, the latter being the payload sent straight to the LINE Messaging API. The message kind is given by type (enum RichMessageType: text, image, tappable_image, flex_message), and the row is bound to a channel through the line_oa_id FK.

The quick_reply_id column is a UUID referencing quick_reply.id, but deliberately has no real foreign key. The application, not a database constraint, is responsible for preventing the deletion of a quick reply that is still in use.

It has 1 : N relationships with campaign and auto_response.

template_message (model TemplateMessage)

Keyed by UUID, holding title and template_message_showcase_id (the template's number in the showcase).

Content is stored twice over: line_template_message JSONB is the payload destined for LINE, while form_template_message JSONB keeps the values the user entered in the CMS form so the editor can be re-rendered later. An index covers (line_oa_id, template_message_showcase_id, status).

An important observation: a template message has no direct send path to LINE. Its flex payload only goes out embedded inside a rich message — which is precisely why quick replies attach at the rich_message level.

quick_reply (model QuickReply)

Keyed by UUID, holding name, items JSONB (the button list, defaulting to []) and status.

One difference to watch for: this table's line_oa_id and organization_id columns are BIGINT, unlike the older tables which use INT. Indexes cover (line_oa_id, status) and (created_date).

  • prisma/schema.prisma:356 — model RichMessage
  • prisma/schema.prisma:761 — model TemplateMessage
  • prisma/schema.prisma:784 — model QuickReply
  • prisma/migrations/20260725100000_add_quick_reply/migration.sql — creates quick_reply and adds rich_message.quick_reply_id, with a comment explaining the missing FK
  • seed-data/11.rich_message.sql
  • schema-dumps/2026-07-24/schema.sql:2667 and :2942

Connections to Other Services

  • cms-api-go — CRUD across all three tables and flex validation against the LINE API; the number of templates is capped by plan_limits.maxTemplates.
  • worker-go — reads rich_message.content when sending a campaign, though the campaign itself also snapshots the content into campaign.rich_message_content at send time.
  • webhook-go — replies with the rich_message bound to an auto-response rule.
  • Connects to Campaign, Auto Response and Tracking, where buttons inside flex payloads are rewritten as tracking links.