Build an AI SMS Agent with Human Takeover
Your customers want to text you. Not download an app, not fill out a contact form, not wait on hold — just text the number on your website and get an answer.
This guide shows you how to put an AI agent behind a real phone number in about ten minutes: two-way SMS powered by any UAPI text agent, automatic STOP/HELP compliance, and — the part most AI SMS products get wrong — a clean way for a human to take over a conversation when the AI hits its limits.
Last updated: June 2026
What You're Building
Customer texts +1 (555) 123-4567
│
▼
Twilio Messaging Webhook → Universal API Channel
│
├── STOP/HELP/START? → handled automatically (agent never invoked)
├── Sender muted? → logged, no auto-reply (human is handling it)
│
▼
Your AI agent (with whatever tools you've given it)
│
▼
Reply sent back as SMS (long replies auto-segmented)Each phone number gets its own persistent conversation with the agent, so "what were we talking about yesterday?" actually works.
Prerequisites
- A Universal API account with a Bearer token (
uapi_ut_*) - A Twilio account with an SMS-capable phone number (~$1.15/month)
- A text agent on UAPI (any agent works — see Creating Agents)
Step 1: Create an SMS-Aware Agent
Any text agent works, but a short system prompt tuned for SMS makes a big difference:
import os
from strands import Agent
from strands.models.bedrock import BedrockModel
def create_agent():
model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-east-1"
)
system_prompt = """You are the SMS assistant for Acme Plumbing.
You answer questions about services, hours, and pricing, and you can
collect callback requests.
SMS rules:
- Keep replies under 300 characters when possible
- No markdown, no bullet lists — plain conversational text
- If a customer is upset or asks for a human, say a team member
will text them shortly and stop trying to solve it yourself
"""
agent = Agent(model=model, system_prompt=system_prompt)
return agent, []The channel injects context like [Channel: SMS | Sender: +1555...] into each message, so the agent knows it's on SMS even without the prompt — but explicit brevity rules keep replies tight.
Step 2: Create the SMS Channel
curl -X POST https://api.universalapi.co/channels \
-H "Authorization: Bearer uapi_ut_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-sms",
"platform": "twilio-sms",
"agentId": "YOUR_AGENT_ID",
"platformConfig": {
"accountSid": "ACxxxxxxxx",
"authToken": "your_twilio_auth_token",
"phoneNumber": "+15551234567"
}
}'The response includes a webhookUrl. Copy it.
Step 3: Point Twilio at the Webhook
In the Twilio Console: Phone Numbers → your number → Messaging Configuration → A message comes in → paste the webhookUrl, method POST. Save.
That's it. Text your number — the agent replies within a few seconds.
Compliance Comes Built In
US SMS regulations (TCPA) are not optional, and they're handled at the platform level so your agent never has to think about them:
| Keyword | What Happens |
|---|---|
STOP | Sender is opted out; confirmation sent; agent is never invoked for them again |
START | Opt-out is cleared; conversations resume |
HELP | Standard help response sent automatically |
Opt-outs are stored per phone number on the channel. Even if a developer misconfigures the agent, an opted-out customer never receives AI messages.
The Killer Feature: Human Takeover
Here's the scenario every AI SMS deployment eventually faces: a customer is frustrated, or the request is too sensitive for a bot, or there's a deal on the line. You need a human in the conversation — without the AI talking over them.
Universal API channels support per-sender mute:
# 1. Mute the AI for this one customer
curl -X POST https://api.universalapi.co/channels/CHANNEL_ID/mute-sender \
-H "Authorization: Bearer uapi_ut_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"senderId": "+15559876543", "reason": "escalation — Dana handling"}'The agent immediately stops auto-replying to that number. Everyone else still gets instant AI responses. Inbound messages from the muted customer are still logged, so the human can see everything.
# 2. The human replies through the same number
curl -X POST https://api.universalapi.co/channels/CHANNEL_ID/send \
-H "Authorization: Bearer uapi_ut_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "+15559876543", "message": "Hi, this is Dana from Acme — I saw your message and I am personally taking care of this."}'From the customer's perspective, it's one seamless conversation on one number. No "you're being transferred," no new thread.
# 3. When it's resolved, hand back to the AI
curl -X POST https://api.universalapi.co/channels/CHANNEL_ID/unmute-sender \
-H "Authorization: Bearer uapi_ut_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"senderId": "+15559876543"}'Check who's currently muted anytime with GET /channels/CHANNEL_ID/muted-senders.
Pro Tip: Let the Agent Request Its Own Takeover
Since UAPI agents can call MCP tools, you can give your agent a tool that mutes its own channel and notifies you (Slack, email, SMS to your personal phone) when a conversation needs a human. The AI literally raises its hand.
Bonus: One Number for Calls AND Texts
If you also want callers to reach a real-time voice agent on the same number, use the twilio-unified platform instead:
curl -X POST https://api.universalapi.co/channels \
-H "Authorization: Bearer uapi_ut_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-front-desk",
"platform": "twilio-unified",
"agentId": "YOUR_TEXT_AGENT_ID",
"voiceAgentId": "YOUR_VOICE_AGENT_ID",
"platformConfig": {
"accountSid": "ACxxxxxxxx",
"authToken": "your_twilio_auth_token",
"phoneNumber": "+15551234567"
}
}'You get two webhook URLs — one for Twilio Messaging, one for Twilio Voice. Callers talk to a Nova Sonic voice agent in real time; texters get the text agent. One number, full coverage.
Session Behavior
By default each phone number gets its own persistent conversation (per-sender), so the agent remembers prior context. You can change this with sessionConfig at channel creation:
sliding-window— conversation resets after an idle period (good for support lines where each issue should start fresh)single-session— everything shares one session (good for an internal team number)
What It Costs
Channels add no extra platform charges — you pay normal agent invocation credits per message (typically a few cents of credits for a Claude-powered reply), plus Twilio's standard SMS rates (~$0.0079/segment in the US).
A small business handling 500 customer texts a month is looking at single-digit dollars.
Go Further
- Channels documentation — all platforms, security options, group policies
- Channels API reference — every endpoint
- Schedule agents with cron — e.g., a nightly digest of SMS conversations
- Voice agents guide — the voice half of
twilio-unified
Building an SMS agent for your business? Show us what you make — or ask questions in r/mcp.