SMS Module¶
The SMS module sends text messages from the platform: confirmation codes, alerts, and marketing campaigns. It queues a message, picks a provider, sends it, then tracks the delivery report and what the message cost.
A message is never sent straight from the request. It goes into a queue first, and the queue is what makes retries, rate limits, send windows and scheduling possible without the caller waiting for the provider.
What it covers¶
- Providers — 11 SMS integrations, each as an adapter with its own credentials schema and capability set.
- Queue — FIFO with attempts, exponential backoff, stuck-message recovery and expiry.
- Routing — the provider is chosen by the destination, with failover to backup profiles.
- Encoding and segments — GSM-7 or UCS-2 is detected, and the text is measured in segments, because that is what the operator bills.
- Templates and triggers — a platform event fires a template, so codes and notifications do not need a caller.
- Delivery reports — final status from the provider, by webhook or by polling.
- Send windows — messages respect the local time of the recipient.
- DNC and opt-out —
STOPreplies and blacklisted numbers are never messaged again. - Spend and balance — per-segment price in reports, provider balance with low-balance warnings.
Methods¶
Names in the table are the TCP command names; the REST column is the HTTP entry point of the same method.
Messages¶
| Method | REST | Description |
|---|---|---|
| SmsSendMessage | POST sms/message |
Queue a message with an explicit text |
| SmsSendFromTemplate | POST sms/message/fromTemplate |
Queue a message rendered from a template |
| SmsSendBulk | POST sms/message/bulk |
Queue up to 10 000 recipients as one campaign |
| SmsResendMessage | POST sms/message/resend |
Send a copy of an existing message |
| SmsCancelMessage | POST sms/message/cancel |
Cancel a message that has not left the queue |
| SmsPreviewMessage | POST sms/message/preview |
Text, encoding, segments and unresolved placeholders without sending |
| SmsGetMessage | GET sms/message/:id |
One message with attempts, error and webhook events |
| SmsGetMessages | GET sms/messages/list |
Message history with filters and aggregates |
| SmsGetStats | GET sms/stats |
Volume, segments, spend and delivery rate |
| SmsGetLastMessagesByParents | — | Last message time for a batch of customers or leads |
Templates and triggers¶
| Method | REST | Description |
|---|---|---|
| SmsAddTemplate | POST sms/template |
Create a text template with placeholders |
| SmsUpdateTemplate | PUT sms/template |
Update a template |
| SmsCloneTemplate | POST sms/template/clone |
Copy a template, for another language |
| SmsDeleteTemplate | DELETE sms/template |
Delete a template |
| SmsGetTemplates | GET sms/templates/list |
Templates of the brand |
| SmsAddTrigger | POST sms/trigger |
Subscribe a platform event to templates |
| SmsUpdateTrigger | PUT sms/trigger |
Update a trigger |
| SmsDeleteTrigger | DELETE sms/trigger |
Delete a trigger |
| SmsFireTrigger | POST sms/trigger/fire |
Fire an event by hand, for testing |
| SmsGetTriggers | GET sms/triggers/list |
Triggers of the brand |
| SmsGetTriggerSubscriptions | GET sms/triggers/subscriptions |
What the module is listening to right now |
Providers, profiles and agents¶
| Method | REST | Description |
|---|---|---|
| SmsGetProviderAdapters | GET sms/providers/adapters |
Implemented adapters, capabilities and credential fields |
| SmsAddProvider | POST sms/provider |
Register an SMS provider |
| SmsUpdateProvider | PUT sms/provider |
Update a provider record |
| SmsDeleteProvider | DELETE sms/provider |
Soft-delete a provider |
| SmsGetProviders | GET sms/providers/list |
Provider catalogue |
| SmsAddProfile | POST sms/providerProfile |
Create a provider profile with credentials |
| SmsUpdateProfile | PUT sms/providerProfile |
Update a provider profile |
| SmsDeleteProfile | DELETE sms/providerProfile |
Soft-delete a provider profile |
| SmsGetProfiles | GET sms/providerProfiles/list |
Provider profiles of the brand |
| SmsGetMyProfiles | GET sms/providerProfile/available/me |
Profiles and templates available to the current manager |
| SmsGetProfileBalance | GET sms/providerProfiles/balance |
Provider balance with low-balance flags |
| SmsAddAgent | POST sms/agent |
Link a manager to a sender identity |
| SmsUpdateAgent | PUT sms/agent |
Update a manager-to-sender link |
| SmsDeleteAgent | DELETE sms/agent |
Remove a manager-to-sender link |
| SmsGetAgents | GET sms/agents/list |
Manager-to-sender links |
Blacklist and send windows¶
| Method | REST | Description |
|---|---|---|
| SmsAddBlacklistPhone | POST sms/blacklist |
Blacklist a number |
| SmsImportBlacklistPhones | POST sms/blacklist/import |
Bulk import up to 10 000 numbers |
| SmsDeleteBlacklistPhone | DELETE sms/blacklist |
Remove a number from the blacklist |
| SmsCheckBlacklistPhone | GET sms/blacklist/check |
Check a number before sending |
| SmsGetBlacklist | GET sms/blacklist/list |
Blacklist entries |
| SmsAddSendWindow | POST sms/sendWindow |
Allowed sending hours for a country |
| SmsUpdateSendWindow | PUT sms/sendWindow |
Update a send window |
| SmsDeleteSendWindow | DELETE sms/sendWindow |
Delete a send window |
| SmsCheckSendWindow | GET sms/sendWindow/check |
Whether a number can be messaged now, and when the window opens |
| SmsGetSendWindows | GET sms/sendWindows/list |
Send windows |
Webhooks and service¶
| Method | REST | Description |
|---|---|---|
| SmsAddWebhook | POST sms/webhook |
Create a public delivery-report URL for a profile |
| SmsUpdateWebhook | PUT sms/webhook |
Update a webhook |
| SmsDeleteWebhook | DELETE sms/webhook |
Delete a webhook |
| SmsGetWebhooks | GET sms/webhooks/list |
Webhooks of the brand |
| SmsGetWebhookEvents | GET sms/webhookEvents/list |
Raw incoming provider events |
| SmsHandleWebhook | POST sms/handleWebhooks/:uuid |
Public endpoint the provider calls |
| SmsPing | GET sms/ping |
Cheap liveness probe with a problem list |
| SmsHealth | GET sms/health |
Full module state: database, adapters, crons, queue, balances |
Entities¶
| Entity | Scope | Purpose |
|---|---|---|
providers |
global | SMS integration catalogue; name is the adapter name |
providerProfiles |
brand | Credentials, default senderId, deliveryUpdateBy, allowed destinations, routingPriority |
agents |
brand | Manager-to-sender link, used by INDIVIDUAL profiles |
triggers |
brand or global | Platform event to template subscription; merge renames event fields |
templates |
brand | Text with placeholders, language, message class |
sms |
brand | Messages, queue and state: status, kind, attempts, encoding, parts, price, campaignId |
webhooks |
brand | Public delivery-report URLs and their secrets |
webhookEvents |
via webhooks |
Raw provider payloads and deduplication |
phoneBlacklist |
brand | DNC numbers and opt-outs |
sendWindows |
brand | Allowed sending hours per country |
Everything is scoped by brand. Desks are not part of the model: a phone number, a
template and a provider profile belong to a brand, not to a department.
The only desk the module ever sees is the one it reads from the platform for the analytics row, and nothing is stored per desk.
Message statuses¶
| Status | Meaning |
|---|---|
PENDING |
In the queue, not sent yet |
IN_PROGRESS |
The queue is handing it to the provider right now |
SENT |
Provider accepted it; the operator has not reported yet |
DELIVERED |
Operator confirmed delivery to the handset |
UNDELIVERED |
Operator reported a failed delivery |
REJECTED |
Provider or operator refused the message |
EXPIRED |
No final report arrived within the allowed window |
FAILED |
Sending failed and no attempts are left |
SENT is not a final status: for an SMS "the provider accepted it" and "the handset got
it" are different events, sometimes hours apart. Statuses are normalised by the module —
every adapter maps its own vocabulary into this set.
Message class¶
Every message has a kind: TRANSACTIONAL or MARKETING.
TRANSACTIONAL |
MARKETING |
|
|---|---|---|
| Send windows | ignored | applied |
Opt-out (STOP) |
ignored | blocks |
| Blacklist, regulator entries | block | block |
A confirmation code must reach the customer at 3 a.m., and an opt-out from promotions is not a refusal to receive login codes. That is the whole reason the two classes exist.
Sending pipeline¶
sequenceDiagram
participant CRM
participant Module as SMS module
participant Queue
participant Provider
CRM->>Module: SmsSendMessage
Module->>Module: phone, blacklist, duplicate, encoding, segments
Module->>Module: send window and schedule
Module->>Queue: PENDING
Queue->>Provider: send (attempt N)
Provider-->>Queue: provider message id → SENT
Provider-->>Module: delivery report (webhook or polling)
Module-->>CRM: sms.message.final
Checks that can reject a message for free run before the provider is contacted: number validity, blacklist and opt-out, duplicate protection, text length in segments, send window and schedule. Then the message is queued, and everything after that — attempts, backoff, failover to a backup profile — happens without the caller waiting.
Events¶
| Event | When |
|---|---|
sms.message.queued |
Message accepted into the queue |
sms.message.sent |
Provider accepted the message |
sms.message.final |
Delivered, undelivered, rejected, expired or failed |
sms.statusChanged |
Notification for the manager UI |