Skip to the page
Conch
DocsGitHub

Decisions

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 with X-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's SmsUrl can 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 its StatusCallback, 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 MediaUrlN on api.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 MessageSid is one message.
  • Conch points the number at the door itself: it sets the number's SmsUrl when 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.