Send a Single SMS
Send a single SMS message to any supported African country.
Endpoint
POST /api/messages/sendRequest
{
"to": "+254712345678",
"text": "Your OTP is 482910. Valid for 5 minutes.",
"sender_id": "MyApp"
}| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone in E.164 format (+254...) |
text | string | Yes | Message content. Up to 160 chars = 1 segment |
sender_id | string | No | Approved sender ID. Defaults to route's default sender |
route | string | No | Route code (e.g. ESMS_UG). Auto-detected from phone number if omitted |
schedule_mode | string | No | "now" (default) or "scheduled" |
scheduled_at | string | If scheduled | ISO 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
{
"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
}{
"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"
}| Field | Description |
|---|---|
id | Unique message ID. Use this to check delivery status |
status | submitted, scheduled, or failed |
segments | Number of SMS segments consumed (160 chars = 1, longer messages split automatically) |
cost | Amount deducted from your wallet, in your wallet currency |
cost_currency | Your wallet's 3-letter ISO currency code |
route_cost | Cost in the route's native billing currency |
route_currency | The route's native currency (e.g. UGX for Uganda routes) |
route | Route code used for delivery |
balance_after | Your wallet balance after the charge |
scheduled_at | ISO 8601 datetime when the message will be dispatched, or null for immediate sends |
Examples
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"
}'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']}")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
| Encoding | Single segment | Multi-segment |
|---|---|---|
| GSM-7 (standard ASCII + Latin) | ≤ 160 chars | 153 chars per segment |
| UCS-2 (Unicode, Arabic, CJK, emoji) | ≤ 70 chars | 67 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.
{
"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"
}| Constraint | Value |
|---|---|
| Minimum lead time | 5 minutes from now |
| Maximum lead time | 7 days |
| Timezone | Always UTC - convert before sending |
Scheduled messages appear in the Logs section of the portal with a scheduled status badge.
Error responses
| Status | Meaning |
|---|---|
| 400 | Phone number could not be parsed or is missing a country code |
| 400 | No active route for the destination country |
| 400 | scheduled_at is missing or less than 5 minutes in the future |
| 401 | Missing or invalid API key |
| 403 | Sender ID not found, not approved, or not approved for the destination country |
| 422 | Wallet balance too low to send this message |
{
"detail": "Sender ID 'MyApp' not found or not approved. Check your sender IDs at /sender-ids"
}{
"detail": "Sender ID 'MyApp' is not approved for UG. Approved countries: KE, TZ"
}{
"detail": {
"code": "insufficient_balance",
"message": "Balance USD 0.50 < estimated cost USD 1.20",
"balance": 0.5,
"cost": 1.2
}
}