eSMS AfricaeSMS Africa
Sending Messages

Bulk Send

Send personalised SMS campaigns to thousands of contacts at once.

Bulk send lets you deliver personalised messages to a contact list. Use it for marketing campaigns, notifications, or announcements.

How it works

  1. Upload a contact list - import a CSV with phone numbers and any custom fields
  2. Pre-check cost - get a per-country breakdown before you commit
  3. Send - choose instant, scheduled, or drip delivery

Pre-check cost

Before sending, call the pre-check endpoint to see a full cost breakdown and confirm your balance is sufficient.

Endpoint
POST /api/messages/bulk-precheck
Request body
{
  "contact_list_ids": [1, 2],
  "text": "Hi {{name}}, you have a new message from {{company}}.",
  "sender_id": "MyApp"
}

Response:

Response 200
{
  "total_contacts": 250,
  "total_segments": 350,
  "total_cost": 1250.50,
  "wallet_currency": "USD",
  "wallet_balance": 5000.00,
  "balance_after": 3749.50,
  "sufficient_balance": true,
  "countries": 3,
  "breakdown": [
    {
      "country_code": "UG",
      "country_name": "Uganda",
      "currency": "UGX",
      "route_code": "ESMS_UG",
      "contacts": 150,
      "segments": 200,
      "route_cost": 7000.00,
      "wallet_cost": 1.88,
      "price_per_segment": 35.00
    },
    {
      "country_code": "KE",
      "country_name": "Kenya",
      "currency": "KES",
      "route_code": "ESMS_KE",
      "contacts": 100,
      "segments": 150,
      "route_cost": 180.00,
      "wallet_cost": 1.39,
      "price_per_segment": 1.20
    }
  ],
  "unroutable_count": 2,
  "unroutable": ["+999000111", "+000123456"]
}

The unroutable list shows numbers we couldn't route - unsupported country codes or invalid numbers. They are skipped during the actual send.

Send bulk

Endpoint
POST /api/messages/send-bulk
Request body
{
  "contact_list_ids": [1, 2],
  "text": "Hi {{name}}, your balance is {{balance}} {{currency}}.",
  "sender_id": "MyApp",
  "schedule_mode": "now"
}
FieldTypeRequiredDescription
contact_list_idsarrayYesIDs of contact lists to send to
textstringYesMessage text. Supports {{variable}} placeholders
sender_idstringNoApproved sender ID
routestringNoForce a specific route (overrides auto-detection)
schedule_modestringNonow (default), scheduled, or drip
scheduled_atstringRequired if scheduledISO 8601 datetime, e.g. 2026-06-01T09:00:00Z
drip_rateintegerRequired if dripMessages per minute (e.g. 60)

Response:

Response 200
{
  "batch_id": "batch-uuid",
  "total_recipients": 250,
  "estimated_cost": 1250.50,
  "status": "sending"
}

Delivery modes

Instant (now)

All messages dispatched immediately in the background. Returns as soon as the batch is queued.

Instant delivery
{ "schedule_mode": "now" }

Scheduled

Messages are held until scheduled_at, then dispatched.

Scheduled delivery
{
  "schedule_mode": "scheduled",
  "scheduled_at": "2026-06-01T09:00:00Z"
}
  • Minimum: 10 minutes in the future
  • Maximum: 90 days in the future
  • Stored securely until dispatch time

Drip

Messages sent at a controlled rate to avoid overwhelming recipients or carrier limits.

Drip delivery
{
  "schedule_mode": "drip",
  "drip_rate": 60
}

drip_rate is messages per minute. A list of 300 contacts at 60/min takes 5 minutes.

Template variables

Personalise messages with column values from your CSV:

VariableResolves to
{{phone}} / {{phone_number}}Recipient's phone number
{{name}} / {{first_name}}Name column from CSV
{{<any_column>}}Any column header from your CSV

Example CSV:

contacts.csv
phone,name,balance,currency
+256712345678,Alice,1500,UGX
+254700123456,Bob,250,KES

Message: Hi {{name}}, your balance is {{balance}} {{currency}}.

Renders as:

  • Alice: Hi Alice, your balance is 1500 UGX.
  • Bob: Hi Bob, your balance is 250 KES.

Examples

Terminal
# Pre-check
curl -X POST https://sms.esmsafrica.io/api/messages/bulk-precheck \
  -H "Authorization: Bearer esms_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact_list_ids":[1],"text":"Hi {{name}}, this is a test."}'

# Send
curl -X POST https://sms.esmsafrica.io/api/messages/send-bulk \
  -H "Authorization: Bearer esms_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_list_ids": [1],
    "text": "Hi {{name}}, this is a test.",
    "schedule_mode": "now"
  }'
bulk_send.py
import httpx

headers = {"Authorization": "Bearer esms_live_YOUR_KEY"}

# Pre-check
check = httpx.post(
    "https://sms.esmsafrica.io/api/messages/bulk-precheck",
    headers=headers,
    json={"contact_list_ids": [1], "text": "Hi {{name}}, this is a test."},
).json()

if check["sufficient_balance"]:
    result = httpx.post(
        "https://sms.esmsafrica.io/api/messages/send-bulk",
        headers=headers,
        json={
            "contact_list_ids": [1],
            "text": "Hi {{name}}, this is a test.",
            "schedule_mode": "now",
        },
    ).json()
    print(f"Batch {result['batch_id']}: {result['total_recipients']} messages")

On this page