eSMS AfricaeSMS Africa
Sending Messages

Send a Single SMS

Send a single SMS message to any supported African country.

Endpoint

Endpoint
POST /api/messages/send

Request

Request body
{
  "to": "+254712345678",
  "text": "Your OTP is 482910. Valid for 5 minutes.",
  "sender_id": "MyApp"
}
FieldTypeRequiredDescription
tostringYesRecipient phone in E.164 format (+254...)
textstringYesMessage content. Up to 160 chars = 1 segment
sender_idstringNoApproved sender ID. Defaults to route's default sender
routestringNoRoute code (e.g. ESMS_UG). Auto-detected from phone number if omitted
schedule_modestringNo"now" (default) or "scheduled"
scheduled_atstringIf scheduledISO 8601 UTC datetime, at least 5 minutes in the future

E.164 format means: + followed by country code, then subscriber number. No spaces, brackets, or dashes. Example: +256712345678.

ISO 8601 format means: YYYY-MM-DDThh:mm:ssZ. Timezone should always be UTC. Example: 2026-06-01T07:00:00Z.

Response

Response 200 - sent immediately
{
  "id": "msg-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "submitted",
  "segments": 1,
  "cost": 1.2022,
  "cost_currency": "KES",
  "route_cost": 35.0,
  "route_currency": "UGX",
  "route": "ESMS_UG",
  "balance_after": 1965.0,
  "scheduled_at": null
}
Response 200 - scheduled
{
  "id": "msg-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "scheduled",
  "segments": 1,
  "cost": 1.2022,
  "cost_currency": "KES",
  "route_cost": 35.0,
  "route_currency": "UGX",
  "route": "ESMS_UG",
  "balance_after": 1965.0,
  "scheduled_at": "2026-06-01T07:00:00+00:00"
}
FieldDescription
idUnique message ID. Use this to check delivery status
statussubmitted, scheduled, or failed
segmentsNumber of SMS segments consumed (160 chars = 1, longer messages split automatically)
costAmount deducted from your wallet, in your wallet currency
cost_currencyYour wallet's 3-letter ISO currency code
route_costCost in the route's native billing currency
route_currencyThe route's native currency (e.g. UGX for Uganda routes)
routeRoute code used for delivery
balance_afterYour wallet balance after the charge
scheduled_atISO 8601 datetime when the message will be dispatched, or null for immediate sends

Examples

Terminal
curl -X POST https://sms.esmsafrica.io/api/messages/send \
  -H "Authorization: Bearer esms_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+256712345678",
    "text": "Hello from eSMS Africa!",
    "sender_id": "eSMSAfrica"
  }'
send_sms.py
import httpx

resp = httpx.post(
    "https://sms.esmsafrica.io/api/messages/send",
    headers={"Authorization": "Bearer esms_live_YOUR_KEY"},
    json={
        "to": "+256712345678",
        "text": "Hello from eSMS Africa!",
        "sender_id": "eSMSAfrica",
    },
)
msg = resp.json()
print(f"Message ID: {msg['id']}, Status: {msg['status']}")
send_sms.js
const resp = await fetch("https://sms.esmsafrica.io/api/messages/send", {
  method: "POST",
  headers: {
    Authorization: "Bearer esms_live_YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+256712345678",
    text: "Hello from eSMS Africa!",
    sender_id: "eSMSAfrica",
  }),
});
const msg = await resp.json();
console.log(`Message ID: ${msg.id}, Status: ${msg.status}`);

SMS segments

EncodingSingle segmentMulti-segment
GSM-7 (standard ASCII + Latin)≤ 160 chars153 chars per segment
UCS-2 (Unicode, Arabic, CJK, emoji)≤ 70 chars67 chars per segment

Messages are automatically split into multiple segments when they exceed the single-segment limit. Each segment is billed separately.

Route auto-detection

If you don't specify a route, the platform detects the destination country from the phone number and selects the appropriate route automatically. Check Coverage & Pricing for supported countries.

Scheduling a single message

To send a message at a future time, pass schedule_mode: "scheduled" and an ISO 8601 scheduled_at timestamp in UTC. The message will be created immediately (balance reserved) and dispatched automatically at the scheduled time.

Schedule for a future time
{
  "to": "+256712345678",
  "text": "Your appointment is tomorrow at 10am. Reply 1 to confirm.",
  "sender_id": "ClinicApp",
  "schedule_mode": "scheduled",
  "scheduled_at": "2026-06-01T07:00:00Z"
}
ConstraintValue
Minimum lead time5 minutes from now
Maximum lead time7 days
TimezoneAlways UTC - convert before sending
Scheduling beyond 7 days is coming soon.

Scheduled messages appear in the Logs section of the portal with a scheduled status badge.

Error responses

StatusMeaning
400Phone number could not be parsed or is missing a country code
400No active route for the destination country
400scheduled_at is missing or less than 5 minutes in the future
401Missing or invalid API key
403Sender ID not found, not approved, or not approved for the destination country
422Wallet balance too low to send this message
403 - sender ID not approved
{
  "detail": "Sender ID 'MyApp' not found or not approved. Check your sender IDs at /sender-ids"
}
403 - sender ID wrong country
{
  "detail": "Sender ID 'MyApp' is not approved for UG. Approved countries: KE, TZ"
}
422 - insufficient balance
{
  "detail": {
    "code": "insufficient_balance",
    "message": "Balance USD 0.50 < estimated cost USD 1.20",
    "balance": 0.5,
    "cost": 1.2
  }
}

On this page