Part 4 of 7 · Missed-call text-back series ~7 min read

How a reply gets routed

The point of the text-back isn’t the text; it’s the reply it earns. When the caller texts “yes please, can you do Thursday?” that message has to reach a real person with the full thread attached. This post is about routing: how an inbound reply is matched to its missed call, threaded, and put in front of the right human.

Key takeaways

  • When the caller texts back, that reply lands on the same Function URL the missed call used — one inbound surface for everything.
  • The reply is matched to its missed call by the caller’s number and threaded, so a person sees the whole conversation, not a stray text.
  • The system never auto-answers a reply. Its job is to put the right human in front of the caller with full context.
  • Anything that reads as urgent — or any reply from an unmatched number — is escalated immediately rather than queued.
  • A STOP reply is honoured the instant it arrives: the number is suppressed, no acknowledgement is sent, and no one is texted again.

The reply is the point

The text-back is only ever the opening line. What actually saves the customer is the reply it earns — “yes please, can you do Thursday?” or “do you take card?” or “actually I need to cancel”. That message has to reach a real person at the business quickly and with the context already attached, because a reply that lands in a void is worse than no text-back at all: it raises the customer’s hopes and then drops them. So routing gets its own piece of the system, and a firm rule: the system threads and delivers replies, it does not answer them.

That rule is deliberate. It would be tempting to let the model carry on the conversation — check the diary, offer a slot, confirm a booking. But that’s exactly where an automated texter starts making promises the business can’t keep, double-booking the Thursday slot, or confidently quoting a price that’s wrong. The system opened the door; a human walks through it. What routing does is make sure the human is the right one and that they’re looking at the whole thread.

One inbound surface, matched and threaded

The provider posts inbound SMS to the same Lambda Function URL that receives missed-call events; the function tells the two apart by the event type in the payload. An inbound reply carries the sender’s number, which is the same key the missed call used, so matching the reply back to its originating call is a direct lookup: find the most recent open call record for that number. That record links to a thread — a row in the threads table that holds the conversation state: which missed call started it, what text-back went out, who (if anyone) is handling it, and whether it’s still open.

Threading matters because it’s what turns a loose SMS into something a person can act on. Without it, the front desk sees “Thursday works” from an unknown number and has no idea what it’s about. With it, they see: missed call at 1:10pm from Priya (last in 3 weeks ago), we texted back offering a booking link, she replied ‘Thursday works’. Every inbound message is appended to its thread, so the history is always in one place, and a reply to a reply stays in the same conversation rather than starting a new one.

Routing an inbound reply: matched to its call, threaded, then triaged to a queue, a person, or escalation On the left, an external box “Caller replies by SMS” sends an arrow into a box “Router (same Function URL)” inside a dotted AWS account container. The router reads a DynamoDB calls table to match the reply to the most recent open missed call for that number, and reads and writes a DynamoDB threads table to append the message to the conversation. From the router, three labelled branches fan out to the right. The first, “normal reply”, goes to a box “Assign to queue / person” that notifies the right human by SES email or a push. The second, “urgent or time-critical”, goes to a box “Escalate now” that pushes straight to the front desk. The third, “unmatched number”, also goes to Escalate now. A separate branch labelled “STOP” goes to a box “Suppress, no reply” that writes the opt-out list. A note reads: the system threads and delivers; it never auto-answers a reply, and a human carries the conversation on. Caller replies inbound SMS AWS account Router same Function URL DynamoDB calls match open call DynamoDB threads append message Assign to queue / person notify by email or push normal reply Escalate now push to the front desk urgent / unmatched Suppress, no reply write opt-out list STOP The system threads and delivers; it never auto-answers a reply, and a human carries the conversation on.
Fig 4. Routing a reply. The inbound SMS hits the same Function URL, is matched to its open missed call and appended to the thread, then triaged: normal replies go to a person or queue, urgent or unmatched ones escalate immediately, and a STOP suppresses the number with no acknowledgement.

Triage: who sees it, and how fast

Once a reply is threaded, the router decides where it goes. Most replies are ordinary — a booking question, a “yes that works”, a quick query — and those are assigned to the right person or shared queue and surfaced however the business already works: an email to the support address, a row in a shared inbox, or a push to the front-desk phone. The settings doc decides the mapping; a two-person shop sends everything to one number, a larger one might split by service.

Two kinds of reply jump the queue. The first is anything that reads as urgent or time-critical — “outside now”, “running late”, “emergency”, “in 20 minutes”. Those are pushed straight to a human immediately rather than sitting in a queue, because a slow reply to a time-critical text is the same as no reply. The second is a reply from a number the system can’t tie to any recent missed call — someone texting the business number cold, or a thread that’s gone stale. Rather than guess, the router escalates it as an unmatched message for a person to read. Detecting “urgent” is kept deliberately simple and deterministic — a keyword and recency check, erring on the side of escalating — rather than asking a model to judge tone, because the cost of under-reacting here is a missed appointment.

STOP means stop

One reply is handled before any routing at all: STOP (and its common variants — “unsubscribe”, “opt out”). The instant the router sees one, it writes the sender’s number to the opt-out list that Part 2 checks, and it does nothing else — no “you’ve been unsubscribed” confirmation, because that would be one more unwanted text. From that moment the number is suppressed everywhere: no text-backs, no escalation texts, nothing. Honouring STOP immediately and silently is both the polite thing and the compliant thing, and keeping the opt-out list as the single source of truth — checked before every send — is what makes the whole system safe to point at real customers.

Design rules that shaped routing

  • One inbound surface. Replies and missed calls hit the same Function URL; the event type tells them apart.
  • Match by the number, thread by the call. Every reply is tied to its open missed call and appended to one conversation.
  • Thread, don’t answer. The system delivers the reply to the right human with full context; it never auto-replies.
  • Urgent jumps the queue. A simple keyword-and-recency check pushes time-critical replies straight to a person.
  • Unmatched goes to a human. A reply with no matching call is escalated to be read, never guessed about.
  • STOP is instant and silent. Suppress the number at once, send nothing back, and check the list before every send.
All posts