Skip to main content

Keyword Auto-response

Overview

When a user sends a message, the system matches it against the keywords an administrator has configured. On a match, it immediately replies with the rich message bound to that keyword.

This is one of the most latency-sensitive features in the platform — the user is sitting there waiting — so it is designed so that keyword matching reads exclusively from a Redis hash and never touches the database. A cron job syncs keywords from the database into Redis every five minutes, and a lazy-warm path covers a cold cache, fixing the classic problem of "the first message after a restart goes unanswered".

Business Flow

  1. Receive a payload containing keyword, lineOaId, organizationId, lineUserId, replyToken, payload, plus the optional fallbackToAi, messageText, and timestamp.
  2. Match the keyword with an HGET against that OA's Redis hash.
  3. Lazy warm — if the keyword is not found, check whether the entire key is missing (HGETALL returns empty).
    • A missing key means the cache is cold, so the OA's keywords are synced from the database immediately and the lookup is retried.
    • A present key without this keyword simply means it was never configured, so there is no need to re-warm on every message.
  4. No keyword match
    • If fallbackToAi = true (the auto_response_first mode), publish onto message_received_trigger so the AI classifier can take over.
    • Otherwise, finish and acknowledge.
  5. Keyword matched — the value stored in Redis is JSON carrying richMessageId and autoResponseId.
    • Load the rich_message by id, scoped to lineOaId.
    • Verify the OA is still active; if not, return mq.Permanent (a 400-class error) and route the message to the DLQ.
    • transformMessageObjects converts the content into LINE message objects.
    • If merge tags such as {{display_name}} or {{custom.xxx}} are present, load the line_user and resolve them.
    • Attach quick replies when the rich message references a quick_reply_id; if that fails to load, the message is still sent, just without the chips.
  6. Delivery strategy, an improvement over the original system which only ever used reply:
    • With a replyToken, use replyMessage first. If the reply fails — for instance because the token expired due to slow processing — fall back to pushMessage so the user still gets an answer.
    • Without a replyToken, which happens on standby events where LINE issues no token, use pushMessage only when the OA has the pushOnStandby flag enabled in message_handling_config.
  7. Record tracking by publishing to tracking_log with content_type = auto_response, action_type = send, and logEventType = webhook.

Keyword sync cron (profile cron-scheduler)

Every five minutes (*/5 * * * *), AutoResponseSyncService.Run pulls all active OAs and rebuilds each one's Redis hash. If a single OA fails, the loop uses continue rather than return — one OA with an oversized or broken configuration must never leave every other OA's keywords cold.

Key Files & Functions

  • internal/autoresponse/service.go
    • Service.ProcessAutoResponse(ctx, payload) — the full feature flow
    • getRedisKey(), which builds keys of the form AUTO_RESPONSE:LINE_OA_ID: plus the id; buildTrackingPayload(), attachQuickReplyAny(), standbyPushEnabled()
    • narrow dependency interfaces: RichMessageFinder, LineOaFinder, LineUserFinder, QuickReplyLoader
  • internal/autoresponse/transform.gotransformMessageObjects(), resolveMergeTags(), hasMergeTags()
  • internal/autoresponse/consumer.goConsumer.OnProcessAutoResponse
  • internal/cronscheduler/auto_response_sync.goAutoResponseSyncService.Run(), SyncKeywordsForLineOa(), and the prefix LINE_MANAGEMENT:AUTO_RESPONSE:LINE_OA_ID:
  • cmd/worker/integration.go — wiring for lineOaForAutoResponse, lineUserFinder, and quickReplyForAutoResponse
  • Queue: consumes line_auto_response; publishes to message_received_trigger and tracking_log (profile main)

Connections to Other Services

  • Receives jobs from: the worker's own line_webhook handler, on text message events
  • Redis (the critical dependency): a hash named LINE_MANAGEMENT:AUTO_RESPONSE:LINE_OA_ID: plus the line OA id, where each field is a keyword and each value is JSON containing richMessageId and autoResponseId
  • Tables: auto_response (the keyword source the cron syncs from), rich_message, line_oa (status, token, and message_handling_config.pushOnStandby), line_user (for merge tags), and the quick reply tables
  • LINE API: POST /v2/bot/message/reply and POST /v2/bot/message/push
  • Connects to: AI Message Intent Classification as the fallback path, and the tracking log pipeline