SMS Agent
Audience: Engineering · Where in app: Settings → AI Agents → SMS Agent · Plan availability: admin gate company.sms_agents_allowed
The text twin of the Outbound Call Agent: each agent is a slot in company.sms_agents with its own instructions, webhook_secret, and sending number (ms-ai elabs_phone_numbers, purpose: 'sms', bound to the agent_key). Send-only — inbound replies are not handled yet.
Model & config
company.sms_agents— Mixed array (schema-evolution freedom; required fields enforced inAiSmsAgentController). Per agent:agent_key,agent_name,agent_instructions,max_length(clamped server-side),webhook_secret.- Gate:
company.sms_agents_allowed(default 0; the SMS card shows when > 0, create enforces it as a hard cap; admin-controlled per company). - Message log:
SmsAgentMessageModel(sms_agent_messages) — one row per send attempt, status honesty-gated likeNoxActionModel:sentonly when Twilio returned a message SID (delivery receipts not tracked), elsefailed/suppressed. Carriesfrom_phone,body(the actual text, for AI sends the model output),ai_generated,context,source(test | webhook | scheduled | api),ghl_contact_id.
Backend routes
| Route | Purpose |
|---|---|
GET/POST /ai-sms-agents, GET/PATCH/DELETE /ai-sms-agents/:agent_key | AiSmsAgentController CRUD (backend/app/routes/aiSmsAgentRoutes.js) |
POST /ai-sms-agents/:agent_key/rotate-secret · /clone | secret rotation, agent clone |
GET /ai-sms-agents/:agent_key/messages | the Logs tab (reads sms_agent_messages) |
POST /ai-sms/webhook | public trigger (AiSmsWebhookController@send) |
Trigger webhook
POST /ai-sms/webhook — { company_id, agent_key, secret_key, to_phone, action, … }:
- Auth:
secret_keymust equal that agent'swebhook_secret(403 on mismatch) — same scheme as the outbound-call webhook. 400 missing fields / unknown action, 404 unknown company/agent. - Actions:
send_sms(caller supplies the literalmessage) ·generate_sms(agent writes the message fromcontextwith its instructions, then sends). - Policy lives in the backend, not ms-ai (mirrors the call path): global do-not-contact via
NoxGhlActionService.isSuppressed(logged assuppressed, never sent), per-agentmax_lengthcap, signature check, and the log row — applied once for every trigger source. sourceis trusted only astest/scheduled; anything else logs aswebhook.
Trigger surfaces
- GHL workflow action (
backend/app/routes/goHighLevelRoutes.js):POST /go-high-level/action/sms-agentsreturns this company's agents as dynamic-field options;POST /go-high-level/action/send-smsexecutes — resolves the company from the GHL payload and relays through/ai-sms/webhook. - Scheduled SMS:
ScheduledNoxActionModelrows withkind: 'sms'(ai/plain modes) fired byScheduledNoxActionJob(schedular, every 5 min, atomic claim) →/ai-sms/webhookwithsource: 'scheduled'; the Twilio message SID is stamped back on the row. Surfaced in the SmsAgent Scheduled tab and the Calls-page scheduled panel. - Outbound call agents can also text via
POST /ai-outbound/send-sms(auth identical to the call webhook; sends from the call agent's number) — see Outbound Call Agent.
ms-ai side
Endpoint (elevenlabs.controller.ts, secret auth) | Does |
|---|---|
POST /elevenlabs/send-sms | sends from the agent's number via the company Twilio subaccount |
POST /elevenlabs/compose-sms | composes (does not send) from instructions + context; billed under cost bucket KnownAgent.SMS |
POST /elevenlabs/import-phone-number/twilio/sms | bind an owned SMS-capable number to an agent (purpose: 'sms') |
POST /elevenlabs/buy-phone-number/twilio/sms | in-app buy into the company subaccount, provisioned for SMS (wallet-gated like voice numbers) |
Metering: twilio-sms-cost:meter cron (number-rental.scheduler.ts, every 10 min) — twin of the call-cost sweep, meterTwilioSmsCosts(), debits Twilio's settled per-message price against the telephony wallet, idempotent per Message SID, app-purchased numbers only. See Telephony › SMS.
Frontend
frontend/src/views/settings/ai/agents/SmsAgent/ + SmsAgentCard.vue — agent list, instructions, number tab, Webhook tab (endpoint + secret + test panel → source: 'test'), Logs tab, Scheduled tab.