Messages & Batches
Send single or bulk template-verified SMS messages and query real-time delivery status for individual messages or batches.
Overview
In API v1, every outbound message is resolved against an APPROVED template — either by explicitly passing template + params, or by passing a raw message string that matches an approved template's compiled regex.
Idempotency Keys
Both POST /api/v1/sms/send and POST /api/v1/sms/bulk accept an optional Idempotency-Key HTTP header. If a request is retried with the same key for the same organization, Emess returns the original response payload with "idempotent": true without re-sending or re-billing.
POST/api/v1/sms/send
Send a single SMS message to one recipient.
Parameters (Body)
| Name | Type | Required | Description |
|---|---|---|---|
| number | string | Yes | Recipient's phone number |
| template | string | If no message | Slug of an approved SMS template (e.g. otp_login) |
| params | object | When template has placeholders | Key-value map of placeholder values validated against the template's paramRules. For public templates, app_name is automatically injected by the gateway; do not include it here. |
| scope | string | No | Pass PUBLIC to force lookup against public templates only |
| message | string | If no template | Raw text string; must match the compiled regex and parameter rules of an approved template |
| keepDiacritics | boolean | No (false) | If true, preserves accents/diacritics in the resolved text |
| sendBefore | string (ISO 8601) | No | Custom UTC expiration timestamp (defaults to the template's priority tier TTL) |
[{app_name}]. The gateway automatically injects your caller app's registered name (max 16 characters). If you pass app_name in params, the API will reject the request with 422 Unprocessable Entity to prevent caller spoofing.Example Request
{"number": "+243990000000","template": "otp_login","params": {"code": "482910"}}
Response (200 OK)
{"message": "SMS sent successfully","id": "msg_uuid","batchId": "batch_uuid","template": "otp_login","type": "critical","cost": 1}
Possible Errors
- 400 Bad Request: Missing recipient/template/message, invalid provider, or invalid
sendBefore. - 401 Unauthorized: Invalid or missing JWT token.
- 402 Payment Required: Insufficient tokens in your organization.
- 403 Forbidden: Template is restricted to another application (
appIdmismatch). - 404 Not Found: Specified
templateslug does not exist. - 422 Unprocessable Entity: Template is not approved, parameter validation failed, or raw message did not match any approved template.
POST/api/v1/sms/bulk
Send the same template-verified SMS message to multiple recipients in a single batch.
Parameters (Body)
| Name | Type | Required | Description |
|---|---|---|---|
| numbers | array[string] | Yes | Array of recipient phone numbers |
| template | string | If no message | Slug of an approved SMS template |
| params | object | When template has placeholders | Key-value map of placeholder values |
| scope | string | No | Pass PUBLIC to force lookup against public templates only |
| message | string | If no template | Raw text string matching an approved template |
| keepDiacritics | boolean | No (false) | If true, preserves accents/diacritics |
| sendBefore | string (ISO 8601) | No | Custom UTC expiration timestamp |
Response (200 OK)
{"message": "Bulk processing completed","total": 2,"data": ["msg_id_1", "msg_id_2"],"succeeded": 2,"failed": 0,"batchId": "batch_uuid","template": "promo_offer","type": "bulk","cost": 2}
GET/api/v1/messages
Cursor-paginated list of outbound SMS messages for your organization (ordered by createdAt desc, id desc). GET /api/v1/sms is also registered as an alias. Supports query filters: status (comma-separated SmsTransactionStatus; see Message Statuses), number (normalized automatically), batchId, appId, from, to, limit, and cursor.
// GET /api/v1/messages?status=SENT,DELIVERED&limit=25{"data": [{"id": "msg_uuid","number": "0990000000","status": "DELIVERED","createdAt": "2026-10-25T08:00:00.000Z","batchId": "batch_uuid","appId": "app_uuid","type": "critical"}],"pagination": {"limit": 25,"nextCursor": null,"hasMore": false}}
GET/api/v1/sms/:id
Fetch the delivery status and parent batch metadata for a single message belonging to your organization (GET /api/v1/messages/:id is also supported as an alias). See the Message Statuses guide for detailed definitions of all status values and automatic refund guarantees.
{"id": "msg_uuid","number": "+243990000000","status": "SENT","batchId": "batch_uuid","createdAt": "2026-10-25T08:00:00.000Z","batch": {"id": "batch_uuid","type": "critical","message": "Your verification code is 482910","sendBefore": "2026-10-25T08:02:00.000Z","transactionId": "idem_key_or_nanoid","appId": "app_uuid","createdAt": "2026-10-25T08:00:00.000Z"}}
GET/api/v1/batches
Cursor-paginated list of message batches for your organization, including an aggregated per-status summary. Supports query filters: type (critical|standard|bulk), appId, from, to, limit, and cursor.
{"data": [{"id": "batch_uuid","type": "standard","message": "Flash sale starts today!","createdAt": "2026-10-25T08:00:00.000Z","sendBefore": "2026-10-25T09:00:00.000Z","appId": "app_uuid","recipientCount": 120,"summary": { "total": 120, "SENT": 100, "DELIVERED": 15, "FAILED": 5 }}],"pagination": {"limit": 25,"nextCursor": null,"hasMore": false}}
GET/api/v1/batches/:id
Retrieve a message batch along with individual message statuses and an aggregated status summary. Note: GET /api/v1/sms/batches/:id is a deprecated alias that returns the same payload along with Deprecation: true and Link: </api/v1/batches/:id>; rel="successor-version" headers.
{"id": "batch_uuid","type": "bulk","message": "Flash sale starts today!","numbers": ["0990000000", "0810000000"],"messages": [{ "id": "msg_1", "number": "0990000000", "status": "SENT", "createdAt": "..." },{ "id": "msg_2", "number": "0810000000", "status": "SENDING", "createdAt": "..." }],"summary": {"total": 2,"SENT": 1,"SENDING": 1}}
GET/api/v1/batches/:id/messages
Cursor-paginated sub-resource for reading recipient messages of a large bulk batch without loading all messages inlined. Accepts status, number, from, to, limit, and cursor.
POST/api/v1/messages/:id/retry
Retry sending a single unsent or failed SMS message by its ID (alias: POST /api/v1/sms/:id/retry). If the message has already been SENT or DELIVERED, Emess will not attempt to send it again and will reply with "retried": false. If the message was previously refunded due to timeout, tokens are re-deducted upon retry; otherwise no extra charge is made.
{"message": "SMS retry initiated successfully","id": "msg_uuid","batchId": "batch_uuid","status": "SENDING","retried": true,"cost": 0}
{"message": "Message has already been delivered","id": "msg_uuid","batchId": "batch_uuid","status": "DELIVERED","retried": false}
POST/api/v1/batches/:id/retry
Retry all unsent and undelivered messages in a batch by its batch ID (alias: POST /api/v1/sms/batches/:id/retry). Messages in the batch that are already SENT or DELIVERED are skipped automatically.
{"message": "Batch retry completed","batchId": "batch_uuid","total": 4,"retried": 2,"skipped": 2,"failed": 0,"data": ["msg_uuid_3", "msg_uuid_4"],"cost": 0}
POST/api/v1/messages/:id/resend
Send a brand-new copy of an existing SMS message (alias: POST /api/v1/sms/:id/resend). By default, it sends to the original recipient number, or you can override the recipient by passing { "number": "+243..." } in the request body. Deducts tokens and creates a new message & batch record.
{"number": "+243819999999","sendBefore": "2026-11-03T21:00:00.000Z"}
{"message": "SMS resent successfully","id": "new_msg_uuid","batchId": "new_batch_uuid","number": "0819999999","type": "standard","cost": 1}
POST/api/v1/batches/:id/resend
Send a brand-new copy of an existing message batch (alias: POST /api/v1/sms/batches/:id/resend). By default, it sends to the original numbers of the batch, or you can override the recipient list by passing { "numbers": ["+243..."] } in the request body. Deducts tokens for all recipients and creates a new batch.
{"numbers": ["+243810000001", "+243990000002"]}
{"message": "Batch resent successfully","batchId": "new_batch_uuid","total": 2,"succeeded": 2,"failed": 0,"data": ["new_msg_1", "new_msg_2"],"type": "bulk","cost": 2}