0076 — SMS: a phone number of the assistant's own, through Twilio
Status: accepted · 2026-10-04
- Status: accepted
- Date: 2026-10-04
- Amends: ADR 0045 (the public door serves one more app)
Context
Every channel so far needs an app on the phone: Telegram, WhatsApp, Signal… A text message needs nothing but a phone, and works where data doesn't. Both OpenClaw and Hermes Agent offer SMS through Twilio.
What Twilio allows (checked 2026-10-04, twilio.com/docs):
- A number is rented per month and can be bought in the Console. A trial account has credit, but texts only numbers verified on it (error 21608), and its texts carry a "Sent from your Twilio trial account" prefix.
- Incoming texts are only delivered to a web address (the number's
SmsUrl), as a form post signed withX-Twilio-Signature: HMAC-SHA1, with the account's Auth Token, of the full address and every field's name and value sorted by name. There is no timestamp. The number'sSmsUrlcan be set through the REST API. - Sending is a REST call (
Messages.json, Basic auth with the Account SID and Auth Token). Up to 1600 characters, billed per 160-character segment. How a text went comes back later to itsStatusCallback, with the carrier's error code: in the US, 30034 when the number isn't registered for A2P 10DLC, 30032 for an unverified toll-free number. - Pictures (MMS) arrive as
MediaUrlNonapi.twilio.com, fetched with the account's key, which redirects to Twilio's file host.
Decision
SMS is a channel like Teams (channels/sms.ts): a number of the
assistant's own, so a bot in ADR 0018's sense. Nobody gets in unless you let
them: you text the number from your own phone, and press That's me in
Conch. Anyone else gets one polite reply and becomes a request. Group texts
are never answered.
The keys are the Account SID (AC…) and the Auth Token, found in
whatever was pasted and checked with Twilio the moment they land. Conch finds
the account's number that can text by itself (IncomingPhoneNumbers), or
says in one sentence how to buy one.
Incoming texts come through the public door (ADR 0045), at the channel's
unguessable /hooks/<id>:
- The signature is checked first, in constant time, against the door's
public address (with and without its port, as Twilio's own libraries do),
before a word is read. The delivery must also name this account. A
repeated
MessageSidis one message. - Conch points the number at the door itself: it sets the number's
SmsUrlwhen the door is ready, and again whenever the door's address changes. An address another program had set is replaced, and that's a "Fixed on its own" note. So there's nothing to paste in Twilio. - The answer to Twilio is an empty TwiML, so Twilio never texts anything by itself.
Answers go out as plain text: Markdown written as words, cut into parts
of at most 1500 characters, at most three texts per answer (each costs), the
rest in Conch. Approvals are a numbered question answered with a number
(TextChoices, as on WhatsApp). A text can't be changed once sent, so a
decided question just stops taking answers.
Delivery problems only a person can fix come back on the status callback and become the channel's state, in plain words with the setting to change: an unregistered US number (30034), an unverified toll-free number (30032), a trial account texting an unverified number, STOP from your phone. The next text delivered clears it.
Pictures come only from this account's own messages on api.twilio.com,
at most 5 MB; the key isn't passed on when Twilio redirects to its file host.
The pieces: ChannelKind and ChannelSecrets gain sms (provider: 'twilio', so another provider can be added beside it), the fields
accountSid and authToken, and ReplaceChannelTokenBody a new Auth Token
on its own. A pretend Twilio (channels/mock/twilio.ts) signs its deliveries
as Twilio does, for tests, E2E and pnpm dev:mock.
Security
- Who can reach it: the internet, at the door, only with a delivery
signed with this account's Auth Token. Forged deliveries are tested
(
sms.test.ts): another token, another address, a field changed after signing, no signature, another account; each is a 403, and none reaches a conversation. Twilio's own published vector checks the signature code. - Who may talk: a phone number is easy to spoof on the carrier network, less so through Twilio, but a stranger who knows the number can text it. So nobody is let in by texting: the owner presses That's me in Conch, and the security checkup lists anyone else let in. A spoofed text from the owner's number would be the owner's: the same holds for any SMS service, and it's said in the guide.
- Keys: the Auth Token lives in the sealed
channels.secrets.json, is listed in Passwords, never logged (redact), and goes only to Twilio's API, in a header. The Account SID is checked to be one before it's put in an address. - The agent can't connect it, let anyone in or open the door, as for every channel.
Consequences
- It costs: about a dollar a month for the number, and a cent or so a text.
- In the US, a number must be registered (A2P 10DLC) or a toll-free one verified before carriers pass its texts on. Twilio walks you through it; until then, the channel's page says why texts don't arrive.
- Vonage, and Android SMS gateway apps, would be other
providers: the channel, its door and its words stay the same.