Broadcast Campaign Delivery (reaching every friend of the OA)
Overview
A broadcast sends a campaign's rich message to every friend of the LINE OA through a single call to the LINE Broadcast API, with no recipient list required. That makes it the cheapest and fastest delivery path, in contrast to multicast, which has to work through the audience group by group.
This job consumes from the line_broadcast_rich_message queue. It validates the campaign and the OA, builds tracking links for every URL and image in the message, transforms the message objects, calls the LINE Broadcast API, and writes the outcome back to the campaign table.
Business Flow
- Receive a payload containing
campaignId,audienceId,organizationId, andlineOaId. - Start the claim heartbeat — a background ticker refreshes
campaign.claimed_atto the current time every 5 minutes during the send, guarded bystatus='sending', so the reaper cannot reclaim the campaign mid-flight. - Validate the campaign —
GetCampaignAndValidateloads the campaign together withrich_message.contentvia a LEFT JOIN, respecting soft deletes.- A
draftorcancelstatus is flipped tofailedand an error is raised. - Any status other than
sendingindicates a duplicate or late redelivery, so the message is discarded quietly as a permanent error with no republish.
- A
- Snapshot the original content — clone the original rich message content so it can be restored if the send fails.
- Validate the OA —
GetLineOAAndValidateresolves the OA and returns a verified or refreshedchannelAccessToken. If the OA is not active, the campaign is flipped to failed. - Build redirect mappings — extract every URL and image URL from the content and replace them with tracked URLs.
- Broadcasts use shared tokens and links, not bound to a
userId, since there is no way to know in advance who will see the message. - The path taken depends on
CAMPAIGN_REDIRECT_MODE: the legacy universal-redirect service or the builtin encrypted token (see Campaign Tracking Links).
- Broadcasts use shared tokens and links, not bound to a
- Transform the message —
TransformMessageObjectsswaps the original URLs for tracked ones throughout the message structure and attaches the quick reply if the campaign references aquick_reply_id. - Call the LINE Broadcast API with a retry key attached.
- Write back the result — update
campaignwithline_message_object,template_tracking,rich_message_content,end_tracking_date(start_dateplus 90 days),total_recipient, and set the status tosent. - On failure —
broadcastFailedrestores the original content, sets the status tofailed, and records areason. Validation and setup errors are logged and then swallowed (returning nil so the message is acked), matching the behaviour of the legacy system.
Key Files & Functions
internal/linemessageapi/broadcast.goBroadcastService.HandleLineBroadcastRichMessage(ctx, payload)broadcastFailed()andendTrackingDateFrom(), which defines the 90-day tracking window- The
LineBroadcastRichMessagePayloadstruct
internal/linemessageapi/consumer.go—Consumer.HandleLineBroadcastRichMessageandwithClaimHeartbeat(), using aclaimHeartbeatIntervalof 5 minutesinternal/linemessageapi/extend.go—extendService.createRedirectMappings(),buildBuiltinMappings(),getGaSettings(),getLineLiffId()internal/linemessageapi/util.go—TransformMessageObjects(),ExtractUrls(),ExtractImageUrls()internal/quickreply/attach.go—LoadItems()for attaching quick repliesinternal/line/messaging.go—Client.Broadcast(messages, retryKey)- Queue:
line_broadcast_rich_messageon themainprofile
Connections to Other Services
- Job sources — the
process_campaignscanner (see Campaign Schedule Dispatch), or cms-api-go when the user sends immediately - Tables —
campaign(read and update),rich_message(content andquick_reply_id),line_oa(token),quick_replyandquick_reply_item - LINE API —
POST /v2/bot/message/broadcastto send, andGET /v2/bot/insight/followersto count followers for the total recipient figure - Redirect service and campaignlink — used to mint tracked URLs
- Downstream effects — link taps insert into
tracking_log, which fires a Postgres trigger intocampaign_click_trigger, and the statistics are computed by Campaign Stat Calculation