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.