Skip to content

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

Step 1: Create an SMS-Aware Agent

Any text agent works, but a short system prompt tuned for SMS makes a big difference:

python
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

bash
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:

KeywordWhat Happens
STOPSender is opted out; confirmation sent; agent is never invoked for them again
STARTOpt-out is cleared; conversations resume
HELPStandard 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:

bash
# 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.

bash
# 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.

bash
# 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:

bash
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


Building an SMS agent for your business? Show us what you make — or ask questions in r/mcp.

Universal API — The agentic entry point to the universe of APIs