Skip to main content

Keyword auto-replies with agent escalation, end to end

A customer texts a keyword and gets an instant, correct answer; a customer texts anything else and lands in front of a person or a deployed AI agent. Individually that is a keyword rule, a thread map, and a routing rule — this tutorial wires the three into one flow with a real keyword tree, then closes the loop with delivery verification and compliance guardrails. The component guides are linked at every stop; this page is the join. You will:
  1. Design the intents
  2. Register the keyword rules
  3. Keep unmatched messages visible in the Inbox
  4. Escalate the unmatched branch
  5. Verify delivery and reply latency
  6. Add compliance guardrails
  7. The keyword tree end to end

1. Design the intents

Decide what a text message against your number can mean before you create a single rule. A health-clinic example that runs this tutorial end to end: Two design rules keep the tree maintainable. First, one intent per keyword: a person who texts HOURS asked one question, so reply with the hours and nothing else. Second, specific rules before broad ones: keyword rules evaluate in creation order, oldest first, and the first match wins — so every specific keyword must exist before the fallback you add in step 4, or the fallback swallows them. STOP is not an intent you manage. Orbit’s built-in consent handling recognizes STOP / START and their multilingual equivalents before any keyword rule is asked — your rules never see those messages, and a custom STOP reply rule would never fire. What you configure is the reply copy and any extra vocabulary on your opt-out rules (step 6).

2. Register the keyword rules

Create the two FAQ intents as Auto-Reply rules on the sms channel. The Auto-Reply Rules guide covers match types, scope, and the full management API; the calls below are the minimal set for this tree. Prerequisites: an SMS-capable number that can receive replies (an alphanumeric sender ID cannot receive inbound), an API key, and the keywords from step 1.
Keep the response text short and end it with a next step (Reply SUPPORT to …) — the menu works because every terminal reply points back into the tree. Matching folds case, so hours, HOURS, and Hours all fire the first rule. A rule applies to every reply-capable number you have on that channel; there is no per-number scoping.

3. Keep unmatched messages visible in the Inbox

A keyword that matches fires its reply and the exchange still lands as ordinary messages on the conversation — the Inbox is the thread surface for all of it, matched or not. The part to wire deliberately is the unmatched no-keyword reply (“when is my refill ready?”): that message must open or continue a conversation your team can see, or it sits unanswered. On the default path this needs no configuration: an inbound SMS that matches no keyword rule continues into the Inbox and becomes a conversation, exactly as described in Two-way SMS conversations. If you run your own webhook-driven thread logic instead, that guide covers the decision point — hold conversations in code while a Map suffices, and upgrade to the Inbox the moment humans, routing, or supervision enter the loop. From that point on, inbound SMS keeps arriving on the same numbers and appears on the queue with no re-wiring. Either way, reply from the same number the customer texted — swapping senders mid-thread splits the conversation into two on the customer’s handset.

4. Escalate the unmatched branch

Three escalation targets, in order of how much structure they add. Pick one per fallback; they are not cumulative. Forward to a deployed AI agent. When the unmatched branch should be answered automatically but needs more than a canned reply, add a catch-all keyword rule — a regex .* created after every specific rule — with action: "forward_agent" and the agent id:
The agent receives the unmatched turn with the inbound message as its context; matched keyword rules still beat it because they were created earlier. If the fallback should instead start a conversation proactively on a segment event rather than reactively on an inbound text, that is the ambient-agent pattern in Ambient agents — a different trigger (segment entry, not an unmatched message), with its own send-once guarantee and audit trail. Route to an Inbox team with a routing rule. When unmatched messages should reach a specific team rather than the general queue, point a routing rule at the SMS channel — the same mechanism Omnichannel queue routing documents for every digital channel:
Lower priority numbers run first; the rule above is the catch-all for the channel, so keep it at a higher number than any team-specific SMS rules you add later. Do nothing. With no catch-all rule and no routing rule, an unmatched inbound simply continues into the Inbox unassigned. Use that while the volume is low; add the routing rule when “who owns this” starts to matter.

5. Verify delivery and reply latency

Two distinct observability questions, one per direction: Did the auto-reply go out? A fired keyword rule produces an outbound message on the same number pair, so both directions are readable from the messages API and the Delivery log. Subscribe to message.delivered and message.failed at the webhook level when you need the receipts in your own systems, and use every message.* payload schema from the webhook event catalog before coding against it. When a receipt does not come back inside the delivery lane’s grace window, the no-receipt safety net ages the message to a terminal status and logs — DLR outcomes monitoring teaches how to read those WARN lines and which ones are designed-benign versus actionable. How fast does a human follow up on the escalated branch? That is an inbox SLA question. Attach an SLA policy so the first-response and next-reply clocks run on every escalated conversation, and fire a breach action — Slack, webhook, reassignment — when a target is crossed. The mechanics live in Inbox SLA timers, and Reading the SLA report is the operator view once timers are attached. The auto-reply side needs no timer: a matched keyword fires in the delivery pipeline, so its latency is a message-send metric, not a human one.

6. Add compliance guardrails

Two tenant-owned controls sit between your keyword tree and the customer; set both before traffic arrives. Opt-out handling. The built-in STOP / START handling already suppresses and re-admits contacts before any keyword rule runs, and suppression is enforced at send time — an outbound Auto-Reply to an opted-out contact fails rather than sending. What you own is the vocabulary and the copy: add brand-specific aliases (QUIT, UNSUB) with their own response text on the Opt-out rules page, covered in Opt-out & opt-in rules. Check the tree against it: a custom alias on the same page gives you the reply a bare STOP carries, without touching the built-in consent semantics. Quiet hours. Keyword Auto-Replies are conversational responses to an inbound, but any escalation path that initiates outreach — a flow, an ambient agent — travels the marketing pipeline and your quiet-hours gate applies there. Decide the posture per channel in Quiet hours: the organization gate blocks or defers sends inside the window you set, and a gated escalation route is blocked with the gate named rather than silently dropped.

7. The keyword tree end to end

The full tree from steps 1–6, evaluated top to bottom on each inbound SMS:
  1. Built-in consent — STOP / START and their equivalents: suppress or re-admit the contact; keyword rules are never consulted.
  2. HOURS (exact) → auto-reply with opening times and the Reply SUPPORT pointer.
  3. LOCATION (exact) → auto-reply with the address, map link, and pointer.
  4. .* (regex, created last) → forward the turn to agent_triage, or reply with a “a teammate will reply shortly” acknowledgement.
  5. No rule matched → the message opens or continues an Inbox conversation; the routing rule assigns it to the support team, and the SLA clock starts.
Run the sequence from a real handset before you announce the number: text each keyword, confirm the sibling inbound/outbound pair in the message API, then text a non-keyword and confirm the conversation lands with the right team and a running first-response clock. Disable the catch-all with { "active": false } instead of deleting it if you want to compare the un-routed baseline.

See also