Bulk email campaigns
One email to a contact group or an explicit list of up to 10,000 addresses. Drafted, sent asynchronously, with live progress counters.
Create a campaign
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Internal label. |
| domain_uuid | uuid | Yes | The verified domain to send from. |
| subject | string | Yes | Subject line. |
| html / text | string | Yes | At least one body. |
| from | string | No | Local part or full address on the domain; omit for the domain default. |
| source_type | string | Yes | group: email every contact in a contact group that has an email address; manual: the explicit list in to. |
| group_uuid | uuid | No | Required for group campaigns. |
| to | string[] | No | Manual audience, up to 10,000 addresses. |
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"
curl -X POST https://api.mobilesasa.com/api/v1/email/campaigns \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "July newsletter",
"domain_uuid": "7c2e91d4-…",
"subject": "What's new at Yourbrand this July",
"html": "<h1>Hello!</h1><p>Here's what we shipped…</p>",
"source_type": "group",
"group_uuid": "9d0b33a1-…"
}'Send it
Creating never sends. The send call queues the campaign for the fan-out worker (a second send call returns 409); recipients are resolved, suppressed addresses skipped, and each remaining recipient is billed one unit as its email goes out at a provider-friendly pace.
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"
curl -X POST https://api.mobilesasa.com/api/v1/email/campaigns/7f31ab02-…/send \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Track progress
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"
curl "https://api.mobilesasa.com/api/v1/email/campaigns/7f31ab02-…" \
-H "Authorization: Bearer $MOBILESASA_TOKEN"{
"success": true,
"data": {
"uuid": "7f31ab02-…",
"name": "July newsletter",
"status": "completed",
"source_type": "group",
"total_recipients": 1874,
"total_sent": 1791,
"total_suppressed": 74,
"total_failed": 9,
"started_at": "2026-07-12T08:00:04Z",
"completed_at": "2026-07-12T08:02:41Z"
}
}Campaign statuses
| Field | Type | Required | Description |
|---|---|---|---|
| draft | initial | No | Created, not sent. |
| queued / sending | in flight | No | Waiting for or being processed by the fan-out worker; counters update live. |
| completed | final | No | Every recipient processed. See the sent/suppressed/failed split. |
| failed | final | No | The campaign stopped; failure_reason says why (most commonly insufficient email balance; recipients already sent stay sent and billed). |
Templates
A template is a body your team has already agreed on, so a campaign does not start from an empty box and get reformatted differently every time.
{
"name": "Monthly statement",
"subject": "Your {{month}} statement",
"html": "<p>Hi {{name}}, your statement is attached.</p>"
}Saving is an upsert on name: posting the same name again replaces the body rather than failing, because saving over a template you already have is the common case. Reading a template gives you subject, html and text to drop straight into a campaign.
Balance covers the whole audience