Support SMS inboxes are usually a routing problem before they are an AI problem.
Someone asks about billing. Someone else needs technical support. A third person wants to talk to sales. The app has to understand the message, pick the right destination, reply to the customer, and remember what happened.
This TypeScript example does that on Telnyx Edge Compute with the Agent SDK.
Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-sms-triage-bot
agent-sms-triage-bot
receives inbound SMS webhooks, classifies each message into one of four topics, looks up the route for that topic, replies by SMS, and stores triage history in durable actor state.
The topics are:
billing
support
sales
general
The default route table maps those topics to queue names:
billing -> billing-queue
support -> support-queue
sales -> sales-queue
general -> general-queue
php
Inbound SMS
-> POST /webhooks/sms
-> TriageAgent.triage(from, text)
-> Telnyx AI Inference classifies topic
-> durable route table lookup
-> SMS reply
-> triage history update
The app uses one TriageAgent
actor per inbound number. That actor stores route rules, recent history, total messages, and topic counts.
POST /webhooks/sms
receives Telnyx message.received
eventsPOST /debug/triage
simulates inbound SMSPOST /routes
updates the route tableGET /routes
lists route rulesGET /history
returns recent triage historyGET /debug/state
inspects actor stateGET /health/liveness
and GET /health/readiness
provide health checksThe core class is TriageAgent
.
It extends the Agent SDK Agent
class and uses durable state for:
The AI classification call uses 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 SMS reply uses the same binding pattern:
await this.env.TELNYX.messages.send({
from: state.fromNumber || state.phoneNumber,
to: from,
text: replyText,
});
That means the example does not hardcode an API key in application code. Messaging and inference both go through this.env.TELNYX
.
After deploying with telnyx-edge ship
, you can test without sending a real SMS:
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?"}'
Example response:
{
"action": "triaged",
"from": "<customer-number>",
"to": "<triage-number>",
"text": "Why was I charged twice this month?",
"topic": "billing",
"route": "billing-queue",
"confidence": 0.95
}
Then inspect the actor history:
curl "https://agent-sms-triage-bot-<id>.telnyxcompute.com/history?number=<triage-number>"
For a small support workflow, this keeps the first version compact:
You can later replace queue names with real integrations: Slack, Zendesk, Salesforce, email, or an internal queue.
Before using this with real customer traffic, I would add:
Resources: