Voice AI

Build an SMS Triage Bot with Durable Agent State

Support inboxes get messy fast.

One customer asks about an invoice. Another reports a bug. Someone else wants pricing. A fourth person sends a vague "can someone help me?" message that still needs to land somewhere useful.

Most teams solve this with layers:

  • an SMS webhook receiver
  • a classifier service
  • a database for routing state
  • a queue or ticketing integration
  • a notification path back to the customer

That architecture works, but it can feel heavy for a simple first version.

This example shows a smaller pattern: use a Telnyx Edge Compute function plus an Agent SDK actor to receive, classify, route, reply, and remember SMS triage state in one deployable app.

Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-sms-triage-bot

What the app does

agent-sms-triage-bot is a TypeScript example that runs on Telnyx Edge Compute.

When a customer sends an SMS, Telnyx sends a message.received webhook to the app. The app passes the sender and message text into a TriageAgent actor.

That actor does four things:

  1. classifies the message as billing, support, sales, or general
  2. looks up the queue for that topic from durable actor state
  3. replies to the customer over SMS
  4. stores the triage entry and updates topic counts

At a high level, the flow looks like this:

Inbound SMS
  -> Messaging webhook
  -> TriageAgent actor
  -> AI topic classification
  -> durable route table lookup
  -> SMS reply
  -> triage history and topic counts

The important bit is ownership.

The actor owns the route table. The actor owns the triage history. The actor owns the topic counts. You do not need to reconstruct that context from a separate app server every time a message arrives.

Why an Agent SDK actor?

The sample uses one actor instance per inbound number.

That means the support number itself gets durable state:

  • route table
  • recent triage history
  • total message count
  • per-topic counts

This is a nice fit for SMS because the channel is asynchronous. Customers can message at any time, and the app needs to remember routing state across requests.

The actor state starts with a default route table:

billing -> billing-queue
support -> support-queue
sales   -> sales-queue
general -> general-queue

Those routes can be changed at runtime with POST /routes.

That is the part I like: the bot is not just stateless glue code around a model. It has an operational memory for how messages should be routed.

The classification step

The TriageAgent classifies each inbound message with Telnyx AI Inference through the [telnyx] binding:

const completion = await this.env.TELNYX.ai.openai.chat.createCompletion({
  model: this.env.AI_MODEL || "moonshotai/Kimi-K2.6",
  messages: [
    { role: "system", content: CLASSIFY_SYSTEM_PROMPT },
    { role: "user", content: `Customer message: "${text}"` },
  ],
  max_tokens: 2000,
  temperature: 0.2,
});

The system prompt asks the model to return JSON only:

{
  "topic": "billing",
  "confidence": 0.95,
  "reason": "The customer is asking about a charge."
}

The code validates the topic against the allowed set and falls back to general if parsing fails or the model returns something unexpected.

That fallback matters. Triage systems should fail into a safe queue, not drop the message.

The route table

Once the topic is known, the actor checks its route table:

const route = state.routeTable[topic] || state.routeTable["general"] || "general-queue";

Then it replies to the customer:

await this.env.TELNYX.messages.send({
  from: state.fromNumber || state.phoneNumber,
  to: from,
  text: replyText,
});

The app uses the Telnyx binding for messaging and inference. In the function code, you call this.env.TELNYX.messages.send() and this.env.TELNYX.ai.openai.chat.createCompletion() without hardcoding an API key.

That keeps the example focused on application logic instead of credential plumbing.

The API surface

The app exposes a small set of routes:

  • POST /webhooks/sms receives Telnyx message.received events
  • POST /debug/triage simulates inbound SMS without using a real phone number
  • POST /routes updates the route table
  • GET /routes returns the current route table
  • GET /history returns recent triage entries and topic counts
  • GET /debug/state inspects actor state
  • GET /health/liveness and GET /health/readiness support health checks

For local testing after deploy, the debug endpoint is the easiest path:

curl -X POST https://agent-sms-triage-bot-<id>.telnyxcompute.com/debug/triage \
  -H "Content-Type: application/json" \
  -d '{"from":"<customer-number>","to":"<triage-number>","text":"Why was I charged twice this month?"}'

You should get back a topic, route, and confidence value.

Then you can inspect history:

curl "https://agent-sms-triage-bot-<id>.telnyxcompute.com/history?number=<triage-number>"

Where this pattern goes next

The sample routes to queue names, but the same pattern could call a real downstream system:

  • create a Zendesk ticket for support messages
  • alert a billing team for invoice disputes
  • send sales leads to a CRM
  • escalate low-confidence classifications for human review
  • route multilingual messages to language-specific queues
  • expose topic counts for a lightweight operations dashboard

The architecture stays the same.

The webhook receives the message. The actor classifies it, looks up the routing rule, sends the reply, and stores the result.

Production notes

Before using this with real customers, I would add:

  • webhook signature verification
  • SMS opt-out and consent handling
  • idempotency for duplicate webhook delivery
  • PII redaction in stored message history
  • role-based controls for changing routes
  • a dead-letter or alerting path when SMS send fails
  • human review for low-confidence classifications

But as a starter architecture, this is a useful building block for AI-assisted support triage.

It shows how to combine messaging, AI inference, and durable state without spreading a small workflow across a pile of separate services.

Resources

  • Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-sms-triage-bot
  • Agent SDK docs: https://developers.telnyx.com/docs/agent-sdk
  • Agent SDK quickstart: https://developers.telnyx.com/docs/agent-sdk/quickstart
  • Roll Your Own Agent: https://developers.telnyx.com/docs/agent-sdk/examples/roll-your-own
  • Edge Compute docs: https://developers.telnyx.com/docs/edge-compute
  • Messaging docs: https://developers.telnyx.com/docs/messaging
  • Telnyx AI Inference docs: https://developers.telnyx.com/docs/inference
  • Telnyx AI skills and toolkits: https://github.com/team-telnyx/ai
  • Telnyx Portal: https://portal.telnyx.com

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations