Skip to content

SMS Messaging ​

Two send surfaces, and they are not interchangeable:

CategoryRouteUse it for
MessagePOST /sms/send_messages/messageTransactional sends, one row per recipient.
BroadcastPOST /sms/broadcastThe 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.

json
{
  "check_spam_msg_mode": "not_check",
  "data": [
    {
      "sender_name": "MyBrand",
      "operator": "ais",
      "phone_number": "+66886578553",
      "message": "Your order has shipped."
    }
  ]
}
  • sender_name must be approved for the carrier.
  • operator is the carrier id (ais, dtac, true_corp).
  • phone_number is E.164.
  • timeout_seconds is accepted for wire compatibility; delivery timeouts are the gateway's policy, so setting it changes nothing.

The response is wrapped in data:

json
{
  "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:

bash
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"
      }'
FieldNotes
idempotency_keyDedupes retries — a replay with the same key returns the original response instead of re-sending the batch.
validity_secondsAsks 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:

bash
curl "$ME_BASE/sms/broadcast/$BATCH_ID" \
  -H "API-Key: $ME_API_KEY" -H "X-TenantID: $ME_TENANT"
json
{
  "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.