An inbound SMS, end to end
A customer texts the business number. The provider posts it, Connect verifies the post, resolves the workspace, deduplicates on the provider's identifier, attaches the message to a Person and lets the agent loop reason about it. The flow then stops short of where email's continues: on the live carrier there is no SMS reply, so the decision that comes out is work on another channel, or an item for a person.
The chain#
- Trigger — a person types a message and sends it to the workspace's business number.
- External event — the provider that carries that number posts the message to Connect's inbound endpoint.
- Authentication and workspace resolution — the post is verified against the provider's own scheme, then the receiving number is resolved to exactly one workspace. Everything after this runs inside it, under the isolation kernel and row-level security.
- Ingest —
sms.pynormalises the payload; the provider-shaped row keeps the original wording of the request, and no provider vocabulary travels further. - Canonical record — a message on a conversation, keyed on the provider's message identifier so a retry updates rather than duplicates.
- Classification and reasoning — the agent loop triages the message in the context of its conversation: an opt-out word short-circuits everything here; anything else is ordinary correspondence.
- Knowledge, memory and rules — bounded extracts from the workspace's Knowledge, the memory tiers that apply to this contact, and any standing directive such as
block:sms. - Autonomy and approval —
autonomy.pyresolves the narrowest applicable rule for thesmschannel. With no carrier for outbound text, the honest outcome of this stage today is not a send decision but a routing decision. - Action through a provider — nothing goes out by SMS. What can go out is a reply on email, WhatsApp or phone, or a follow-up booked on one of those, under that channel's own rule.
- Result — recorded with evidence. A send is sent only on a provider acknowledgement; a refusal is recorded with its reason rather than dropped.
- Relationship, timeline and memory — the text joins the Person's history beside their email and their calls, and anything durable is written as memory instead of being left in the conversation.
- Audit, usage and Needs You — the decision and the rule behind it are in the audit trail, any send is metered on a customer plan, and anything a person must handle is queued for them.
- Next — the conversation stays open. A later text from the same number joins it; a reply on another channel is linked to it, so the relationship reads as one exchange rather than two disconnected ones.
Stage by stage, with the failures#
| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Webhook arrives | Nothing yet | A provider-shaped row | No webhook configured at the provider; the post cannot be verified |
| Workspace resolution | Nothing yet | The scope everything else runs in | The receiving number maps to no workspace — the message is a dead letter |
| Deduplication | One conversation, not two | The existing message is updated on a retry | The provider issues a different identifier per attempt |
| Person resolution | The message on a contact | An identity match, or a new contact | Caller identity withheld; a recycled number attaching to its previous owner |
| Opt-out scan | A suppression on the contact | A block:sms memory row and an audit entry | Nothing — this is the most reliable step in the chain |
| Reasoning | Priority and classification on the conversation | Triage state the engine reads when picking work | A message with no actionable content; ambiguity that should go to a person |
| Decision | An item in Needs You, or a follow-up | A commitment with a date and a reason | An autonomy rule set to off for every channel that could carry the answer |
| Answer | A sent email, WhatsApp message or call | Provider evidence on the record | No SMS reply is possible at all on the live carrier |
Where this flow differs from the email one#
Three places. The identity is a mobile rather than an address, so resolution is likelier to produce a new contact for someone you already know. There is no thread header to follow, so conversation grouping is by the pair of numbers rather than by a threading identifier the sender supplies. And the flow terminates in a different medium, which is unusual enough to be worth saying twice: the answer to a text goes out by email, WhatsApp or phone.
That third difference is not a workaround dressed up as design — it is the consequence of a foundation channel, and the reason a business should decide deliberately whether text is an acceptable intake route for the kind of question its customers send.
Questions#
Does the whole chain run for a stop request?
No, and deliberately. An opt-out short-circuits at the classification stage: the suppression is written, the audit entry is made, and no draft is prepared against a person who has just asked not to be contacted. An SMS STOP, end to end follows that shorter path.
How long does the chain take?
Ingest is a webhook and a few writes and is not where the time goes. The reasoning stage runs on the engine's own schedule, and any human decision waits for a person. No published measurement of end-to-end latency for this channel exists, so this page does not offer one.
Can I see the whole chain for one message afterwards?
The conversation shows the message and what followed; the timeline shows it beside everything else about that person; the Decision Log shows what was decided and under which rule. Between them the chain is reconstructable without reading anything internal.