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
- In Frona, open Settings → Channels and click Add channel.
- Pick Twilio SMS.
- Choose the space the conversation will live in and the agent that will respond.
- Fill in the Twilio fields (defaults pull from
voiceconfig if you've set up Twilio voice). - Save.
The adapter registers a Twilio Messaging webhook on the phone number and moves the channel into Pairing.
Pairing your number
After connecting, the channel shows a one-time pairing code. Text the code from your phone to the Twilio number you're connecting. The adapter records your phone number as a paired address, and the channel transitions to Connected.
From then on, SMS from that number is treated as you and runs full inference. Other senders are dropped unless you add a policy that allows them. To pair another phone, repeat the flow; paired addresses accumulate.
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.
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.