Message Logs
Query paginated message history with filters for status, recipient, and date range to reconcile delivery.
#List message logs
GET
/v1/sms/logsJWT| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 25 | Results per page |
status | string | — | Filter: sent, delivered, failed, … |
to | string | — | Filter by recipient number |
from_date / to_date | date | — | Date range filter |
bash
bash
curl -s "https://api.sendafrica.online/v1/sms/logs?page=1&per_page=10&status=delivered" \
-H "Authorization: Bearer $JWT_TOKEN"200 OK
json
{
"success": true,
"data": {
"items": [
{
"id": "SA-f3b1c2d49e8a4f2bb1c2d3e4f5a6b7c8",
"recipient": "+255712345678",
"message": "Your OTP is 482910...",
"sender": "MyBrand",
"status": "delivered",
"credits_used": 1,
"campaign_id": null,
"created_at": "2026-06-11T16:24:05Z",
"delivered_at": "2026-06-11T16:24:09Z"
}
],
"total": 143,
"page": 1,
"per_page": 10,
"total_pages": 15
}
}#Delivery lifecycle
The send call is synchronous — by the time you receive a response, the message has already been submitted to the gateway. Delivery updates arrive later via webhook.
statuses
text
send response: sent ──▶ delivered
(or failed) ├──▶ pending ──▶ delivered
│ └──▶ failed
└──▶ failedsent— accepted by the gateway; the immediate send response for a successful submit.pending— the carrier buffered or queued the message; it may still be delivered.delivered— handset confirmed receipt, via the delivery webhook.failed— rejected at submit time, rejected/expired at the carrier, or undelivered; credits were already refunded at gateway-rejection time where applicable.
Webhooks vs polling
Polling logs works, but webhooks give you sub-second delivery confirmation without burning rate limit. Use polling as a fallback reconciliation job (e.g. hourly), not as your primary signal.
#Reconciliation pattern
reconcile.ts
typescript
// Hourly job: find messages stuck in "sent" older than 10 minutes
// and refresh their state from the log.
async function reconcileStuckMessages(jwt: string) {
const cutoff = new Date(Date.now() - 10 * 60_000).toISOString();
const res = await fetch(
"https://api.sendafrica.online/v1/sms/logs?status=sent&per_page=200",
{ headers: { Authorization: `Bearer ${jwt}` } },
);
const { data } = await res.json();
for (const msg of data.items) {
if (new Date(msg.created_at) < new Date(cutoff)) {
await escalate(msg.id); // alert / re-check / mark unknown
}
}
}