SMS Messaging
Two send surfaces, and they are not interchangeable:
| Category | Route | Use it for |
|---|---|---|
| Message | POST /sms/send_messages/message | Transactional sends, one row per recipient. |
| Broadcast | POST /sms/broadcast | The same message to many recipients. |
Broadcast is a deployment opt-in
/sms/broadcast is not mounted unless the deployment sets ME_SMS_PRODUCER_ENABLED. An environment without it answers 404 — not an error you can act on. Check with your operator before building against it.
Send a message
Each row carries its own sender, operator, recipient, and body. Rows sharing operator + sender name + message are fanned out as one batch each.
{
"check_spam_msg_mode": "not_check",
"data": [
{
"sender_name": "MyBrand",
"operator": "ais",
"phone_number": "+66886578553",
"message": "Your order has shipped."
}
]
}sender_namemust be approved for the carrier.operatoris the carrier id (ais,dtac,true_corp).phone_numberis E.164.timeout_secondsis accepted for wire compatibility; delivery timeouts are the gateway's policy, so setting it changes nothing.
The response is wrapped in data:
{
"data": {
"accepted": 1,
"rejected": 0,
"batches": [
{
"batch_id": "018f7b2a-7c3e-7a1b-9c2d-1a2b3c4d5e6f",
"accepted": 1,
"rejected": 0
}
]
}
}accepted counts recipients queued for the carrier. rejected counts those dropped before queueing — a blank or duplicate number, or a publish error. One bad number never fails the whole request.
Broadcast
One message, one operator, one sender, many recipients:
curl -X POST "$ME_BASE/sms/broadcast" \
-H "API-Key: $ME_API_KEY" \
-H "X-TenantID: $ME_TENANT" \
-H "Content-Type: application/json" \
-d '{
"operator": "ais",
"sender_name": "MyBrand",
"message": "Sale ends tonight.",
"recipients": ["+66886578553", "+66886578554"],
"idempotency_key": "campaign-2026-09-12"
}'| Field | Notes |
|---|---|
idempotency_key | Dedupes retries — a replay with the same key returns the original response instead of re-sending the batch. |
validity_seconds | Asks the carrier to drop the message if undelivered in this window. Honoured by DTAC and True; AIS has no expiry element, so it is ignored there. |
Then poll the batch:
curl "$ME_BASE/sms/broadcast/$BATCH_ID" \
-H "API-Key: $ME_API_KEY" -H "X-TenantID: $ME_TENANT"{
"batch_id": "…",
"total": 2,
"delivered": 1,
"sent": 1,
"failed": 0,
"timeout": 0,
"pending": 0
}undeliverable_chars
A batch may come back with undeliverable_chars — codepoints the carrier that carried it is known to destroy, listed as "U+1F381 U+1D7EE".
It exists because the loss is otherwise invisible from the outside: the carrier accepts the message, the surrogate pair is billed as two units, and the handset shows nothing there. If you see it, substitute a character that survives.
Older SMS endpoints
Earlier documentation described /sms/send_messages/{otp,broadcast,eagleeye,cancel}. Those routes are not part of the current API. OTP verification moved to Verify / OTP; broadcast is /sms/broadcast above.
