Connect a Twilio phone number and one of your agents will reply to inbound SMS. The same channel can also be used in Signal mode to capture verification codes and route them to whichever agent is waiting.
What you need
- A Twilio account with an SMS-capable phone number
- The Account SID and auth token from the Twilio console
- A publicly reachable URL for the engine. See
server.external_url. For local development, ngrok works.
Connecting the channel
Connection happens in three stages. A short dialog creates the channel, you fill in the Twilio fields on the detail page, then you pair your phone number by texting a one-time code.
Step 1: Create the channel
- In Frona, open Settings → Channels and click Add channel.
- Pick Twilio SMS from the provider list. The create dialog opens.
- Fill in the dialog:
- Space. Defaults to
Twilio SMS. This is the space the conversation will live in. Keep the default to create a new space with that name, type any other name to create a different one, or pick an existing space from the dropdown. A space can host only one channel, so spaces already bound to another channel are hidden. - Agent. The agent that reads every incoming message and sends the reply.
- When a message arrives. How inbound messages are handled:
- Treat as a message from you (default). The agent runs full inference and replies. Pair your phone in step 3 before this works.
- Hand off to a waiting agent. The message goes to whichever task is currently waiting on a signal (e.g., a bank 2FA code). The agent does not reply on this channel. See Signals.
- Space. Defaults to
- Click Create.
You land on the channel detail page. The status is Setup until the Twilio config is filled in.
Step 2: Configure Twilio
Open the Config tab on the channel detail page. The Provider Config panel lists three fields. If you've already configured Twilio voice, the fields show Using server default and pull from voice.twilio_* in the config file. Otherwise:
For from_number (the Twilio phone number, in E.164 format like +15551234567):
- Type the value directly into the text field. It's not a secret.
For account_sid and auth_token (both secrets, set via Frona's credential picker):
- Click Configure next to the field.
- Pick a vault connection. You can use a configured password manager (1Password, Bitwarden, HashiCorp Vault, KeePass) or Local to keep the secret in Frona's encrypted store.
- Either pick an existing vault item, or click New credential to create one. For Local, choose API key, name it, and paste the SID or token from the Twilio console.
- Pick the field on the item that holds the value.
- Click Save in the dialog.
After all three fields are populated, click Save at the top of the channel page. The adapter registers a Twilio Messaging webhook on the phone number and the channel transitions to Connected.
Step 3: Pair your phone number
By default the channel doesn't trust any sender, so even SMS from your own phone is ignored. Pair the channel to your number so the agent treats you as a self-source:
- On the channel detail page, click Pair. A short code appears (e.g.
XYZ-123). - Text the code from your phone to the Twilio number you're connecting.
- The channel records your number as a paired address and the conversation goes live.
From then on, SMS from that number is treated as you and runs full inference. To pair another phone, click Re-pair and repeat; paired addresses accumulate. Other senders are dropped unless you add a policy that allows them.
Configuration fields
| Field | Required | Description |
|---|---|---|
account_sid | Yes | Twilio Account SID (starts with AC). Defaults from voice.twilio_account_sid if set. |
auth_token | Yes | Twilio auth token. Defaults from voice.twilio_auth_token if set. Stored encrypted. |
from_number | Yes | The Twilio phone number to send from, in E.164 format (e.g., +15551234567). Defaults from voice.twilio_from_number if set. |
How messages flow
- Inbound SMS to your Twilio number is delivered to Frona via webhook.
- The agent runs through its tool loop and composes a single reply.
- The adapter sends one outbound SMS per turn (tool calls are folded into the same body, so you pay for one message per turn).
- Twilio status callbacks update the message state in the UI: queued → sent → delivered.
The single-message-per-turn behavior is deliberate: SMS billing is per-segment, and splitting tool progress into separate texts is expensive.
Replies are capped at 1,600 characters. Anything longer is truncated and finished with Full reply: {short-link}, where the short link points at a share URL for the full conversation. The same share link is reused across overflows in the same chat.
Approvals and questions
When the agent needs your input — an app deploy approval, a question with multiple choices — the prompt arrives as a plain SMS with a Reply YES or NO (or the option labels) hint. Quote-reply with yes, y, ok, 👍, no, n, nope, ❌, or any of the common variants and the agent resumes. The chat stays paused until you reply.
Signal mode
Switch the channel's dispatch mode to Signal in its settings to use it for capture-only:
- Inbound messages are annotated and run against active signal watches.
- The channel does not reply.
- This is the right setup for "wait for the verification code" flows where the agent that asked is in a different space.
You can leave a single SMS channel in Signal mode and have any agent wait on it via await_signal.
Allowlisting senders
By default only paired addresses pass receive_message. To allow another contact (e.g., a partner's phone), add a policy:
permit(
principal == Policy::Agent::"assistant",
action == Policy::Action::"receive_message",
resource in Policy::Channel::"sms"
) when { resource.sender.address == "+15559876543" };To block a known spam shortcode while still allowing it to feed signal matching:
forbid(
principal,
action == Policy::Action::"receive_message",
resource
) when { resource.sender.address == "22000" };See Policies for more patterns.
Tips
- Pair before you test. Until the channel is paired, even your own messages are dropped.
- Use
voice.*defaults if you already have Twilio configured. The SMS form auto-fills from those values. - One Twilio number can only do one mode at a time. If you switch the channel from Message to Signal, the agent stops replying to that number.
- Use ngrok for local development. Twilio won't deliver webhooks to localhost.
- Watch the status callback URL. If outbound messages stay
queued, the status webhook isn't reaching the engine.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Channel marked Failed right after creation | Bad credentials, the number isn't SMS-capable, or the engine's external URL isn't reachable |
| Stuck in Pairing | The pairing SMS never arrived at Twilio. Check Twilio's logs for inbound messages. |
| Inbound from your phone is ignored | The number you texted from doesn't match the paired address. Re-pair. |
Outbound stays queued forever | Status callbacks aren't reaching the engine. Check server.external_url. |
Next steps
- Channels. How channels work in general.
- Signals. Channels in Signal mode feed signal matching.
- Twilio Voice. Same provider, different feature.
- Policies. Allowlist or block specific senders.