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 —
STOP replies 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
Providers, profiles and agents
Blacklist and 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.
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 |