V1
API v1•Messages & Batches

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)

NameTypeRequiredDescription
numberstringYesRecipient's phone number
templatestringIf no messageSlug of an approved SMS template (e.g. otp_login)
paramsobjectWhen template has placeholdersKey-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.
scopestringNoPass PUBLIC to force lookup against public templates only
messagestringIf no templateRaw text string; must match the compiled regex and parameter rules of an approved template
keepDiacriticsbooleanNo (false)If true, preserves accents/diacritics in the resolved text
sendBeforestring (ISO 8601)NoCustom UTC expiration timestamp (defaults to the template's priority tier TTL)
Public Template Attribution: All public templates automatically prefix messages with [{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

POST /api/v1/sms/send
{
"number": "+243990000000",
"template": "otp_login",
"params": {
"code": "482910"
}
}

Response (200 OK)

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 (appId mismatch).
  • 404 Not Found: Specified template slug 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)

NameTypeRequiredDescription
numbersarray[string]YesArray of recipient phone numbers
templatestringIf no messageSlug of an approved SMS template
paramsobjectWhen template has placeholdersKey-value map of placeholder values
scopestringNoPass PUBLIC to force lookup against public templates only
messagestringIf no templateRaw text string matching an approved template
keepDiacriticsbooleanNo (false)If true, preserves accents/diacritics
sendBeforestring (ISO 8601)NoCustom UTC expiration timestamp

Response (200 OK)

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
// 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.

GET /api/v1/sms/:id
{
"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.

GET /api/v1/batches
{
"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.

GET /api/v1/batches/:id
{
"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.

Response (200 OK - Retried)
{
"message": "SMS retry initiated successfully",
"id": "msg_uuid",
"batchId": "batch_uuid",
"status": "SENDING",
"retried": true,
"cost": 0
}
Response (200 OK - Already Sent / Delivered)
{
"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.

Response (200 OK)
{
"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.

Optional Request Body
{
"number": "+243819999999",
"sendBefore": "2026-11-03T21:00:00.000Z"
}
Response (200 OK)
{
"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.

Optional Request Body
{
"numbers": ["+243810000001", "+243990000002"]
}
Response (200 OK)
{
"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
}