eSMS AfricaeSMS Africa
API ReferenceSMS API

Bulk SMS

POST /api/messages/send-bulk - Send to a contact list or an inline batch, with scheduling and drip.

Endpoint

POST https://sms.esmsafrica.io/api/messages/send-bulk

Send one message to many recipients in a single call. Supply recipients either inline (recipients) or by referencing saved contact lists (contact_list_ids). The message body can carry {{variable}} placeholders that are filled per recipient. The call returns a batch_id immediately and delivery happens in the background - track it with Batch status.

Authentication

Authorization: Bearer esms_live_your_api_key

Request body

{
  "recipients": [
    { "to": "+254712345678", "name": "Amina", "vars": { "code": "4821" } },
    { "to": "+256772123456", "name": "Brian", "vars": { "code": "9930" } }
  ],
  "text": "Hi {{name}}, your code is {{code}}.",
  "sender_id": "MyApp",
  "schedule_mode": "now"
}
FieldTypeRequiredDescription
recipientsarrayYes*Inline list of { to, name?, vars? }. vars fills {{key}} placeholders; {{name}} and {{phone}} are always available.
contact_list_idsarray of intYes*Saved contact list IDs to send to instead of (or as well as) recipients.
textstringYesMessage body. Supports {{variable}} substitution; rendered per recipient and billed per segment.
sender_idstringNoApproved sender ID. Must be approved for each destination country, else it falls back to the route default.
routestringNoForce a public route code (e.g. ESMS_UG); otherwise auto-detected per number.
schedule_modestringNonow (default), scheduled, or drip.
scheduled_atstringNoISO 8601 UTC time, required when schedule_mode is scheduled (5 min - 7 days ahead).
drip_rateintegerNoMessages per minute, required when schedule_mode is drip.

* Provide at least one of recipients or contact_list_ids.

Duplicate numbers are collapsed, and anyone on your opt-out (STOP) list is dropped before sending.

Response

{
  "batch_id": "b6c2f1e0-1d2c-4a55-9b3f-8e6b0c2d4a11",
  "total_recipients": 2,
  "estimated_cost": 0.0192,
  "status": "sending"
}
FieldDescription
batch_idPass to Batch status or the batch_id filter on List messages.
total_recipientsUnique, non-opted-out recipients after deduplication.
estimated_costTotal charged to your wallet, in your wallet currency.
statussending, scheduled, or dripping depending on schedule_mode.

Errors

StatusDescription
400No recipients, missing text, or an unroutable country.
402Insufficient balance (required, available, segments, recipients).
403Sender ID not approved for a destination country.
413A rendered message exceeds the segment cap (max_segments).

Example

curl -X POST https://sms.esmsafrica.io/api/messages/send-bulk \
  -H "Authorization: Bearer esms_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "recipients": [
      { "to": "+254712345678", "name": "Amina", "vars": { "code": "4821" } }
    ],
    "text": "Hi {{name}}, your code is {{code}}.",
    "sender_id": "MyApp"
  }'

Pre-check a bulk send

POST https://sms.esmsafrica.io/api/messages/bulk-precheck

Runs the same recipient gathering, deduplication and opt-out suppression as send-bulk, but only returns a cost estimate - nothing is sent or charged. Use it to preview spend and per-country reach before committing.

Request body

Same as send-bulk but only contact_list_ids/recipients, text and sender_id are read:

{
  "recipients": [{ "to": "+254712345678" }, { "to": "+256772123456" }],
  "text": "Hi {{name}}, your code is {{code}}."
}

Response

{
  "total_contacts": 2,
  "total_segments": 2,
  "total_cost": 0.0192,
  "wallet_currency": "USD",
  "wallet_balance": 12.50,
  "balance_after": 12.4808,
  "sufficient_balance": true,
  "countries": 2,
  "breakdown": [
    {
      "country_code": "KE",
      "country_name": "Kenya",
      "currency": "KES",
      "route_code": "ESMS_KE",
      "contacts": 1,
      "segments": 1,
      "route_cost": 1.2,
      "wallet_cost": 0.0096,
      "price_per_segment": 1.2
    }
  ],
  "unroutable": [],
  "unroutable_count": 0,
  "suppressed_count": 0
}
FieldDescription
breakdown[]Per-country reach, segments and cost (route + wallet currency).
balance_afterYour wallet balance if you sent this batch now.
suppressed_countRecipients dropped because they are opted out (STOP).
unroutable / unroutable_countNumbers with no active route (up to 10 samples).

Tips

  • Call bulk-precheck first to confirm reach and cost, then send-bulk with the same body.
  • Use a esms_test_ key to simulate a blast end to end - members are created and reach a final status, but nothing is charged or delivered.

On this page