Campaigns
Schedule bulk SMS campaigns against a contact list, track live per-recipient stats, cancel or delete, and understand the worker loop.
Campaigns are scheduled bulk sends linked to a contact list. A dedicated worker process polls for scheduled campaigns every 30 seconds and executes them with correct per-recipient credit accounting.
#Endpoints
| Method | Path | Description |
|---|---|---|
| GET / POST | /v1/campaigns/ | List (with live stats) / create & schedule |
| GET | /v1/campaigns/{id} | Detail with live stats |
| POST | /v1/campaigns/{id}/cancel | Cancel a draft/scheduled campaign |
| DELETE | /v1/campaigns/{id} | Delete a campaign that never sent anything |
| GET | /v1/campaigns/{id}/recipients?status= | Per-recipient tracking |
#Create a campaign
/v1/campaigns/JWTcurl -s -X POST https://api.sendafrica.online/v1/campaigns/ \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: promo-june-launch" \
-d '{
"name": "June Promo Blast",
"message": "Mambo! Enjoy 20% off all weekend. Karibu!",
"contact_list_id": 3,
"scheduled_at": "2026-06-20T09:00:00Z",
"sender_id": "MyBrand"
}'Register your sender ID first
sender_id must be registered and authorized — omit it to send from the platform default SendAfrika. Unregistered IDs are rejected by the carrier at send time and the message is re-sent under the platform fallback, so a whole campaign could silently lose your branding. See Sender IDs.
{
"success": true,
"data": {
"id": 42,
"name": "June Promo Blast",
"status": "scheduled",
"recipients_count": 482,
"estimated_credits": 482,
"scheduled_at": "2026-06-20T09:00:00Z"
}
}Idempotent creation
Campaign creation supports Idempotency-Key. Double-clicking "Schedule" in a UI or retrying after a timeout will not create duplicates.
#Live stats
/v1/campaigns/{id}JWT{
"success": true,
"data": {
"id": 42,
"status": "processing",
"total_recipients": 482,
"sent": 310,
"delivered": 295,
"failed": 4,
"pending": 168,
"credits_spent": 310,
"cost_tzs": 10850
}
}#Per-recipient tracking
/v1/campaigns/{id}/recipients?status=failedJWTFilter by sent, delivered, failed, or pending to build delivery dashboards or build a retry list of failed numbers.
#How execution works
- The worker polls for
scheduledcampaigns every 30 s. - It acquires a Redis distributed lock — already-running campaigns are skipped, so multiple workers are safe.
- The campaign is marked
processing, then contacts are fetched from the linked list. - Each recipient is sent individually via the single-send path, in chunks of 500 — intentional, so per-recipient credit accounting stays exact.
- The campaign is marked
completed(orfailedif its list was missing/empty), and the lock is released.
Cancellation window
Only draft or scheduled campaigns can be cancelled — once the worker flips it to processing, sends are underway. Delete is only allowed for campaigns that never sent anything.