SMS campaigns send the same message (or templated variations) to many recipients in one API call. Useful for promotions, appointment batches, announcements, and reminders.
Campaigns are one-time broadcasts. They send the message and stop. If a recipient replies, the reply is handled by your agent using its system_prompt — not by the campaign itself.
How replies work
Replies to a campaign SMS are handled by the agent’s system_prompt — the same system that handles all inbound SMS. Mention the campaign context in your agent prompt before launching so the agent knows what’s going on.
For example:
You are the receptionist at Acme Dental. We are running a March promotion: 20% off cleanings, valid weekdays 9am-5pm next week. If a customer replies YES, confirm the offer and ask which day suits them.
When someone replies “YES” — or texts asking about the offer — the agent already knows what to say.
For the full reply behaviour (caller history, variables, conversation context), see SMS replies.
Create a campaign (API)
curl -X POST https://api.nixflex.com/v1/sms/campaigns \
-H "Authorization: Bearer KEY_ID:KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_125207e452f8714a",
"from_number": "+447446466847",
"name": "March promotion",
"message_template": "Hi {{first_name}}, Acme Dental offering 20% off cleanings this month. Reply YES to book.",
"recipients": [
{ "phone": "+447111000001", "variables": { "first_name": "Sarah" } },
{ "phone": "+447111000002", "variables": { "first_name": "James" } }
],
"schedule_type": "now"
}'
Response:
{
"campaign_id": "smsc_a1b2c3d4",
"status": "running",
"total_count": 2,
"delivered_count": 0,
"failed_count": 0,
"pending_count": 2,
"source": "api"
}
Personalization with placeholders
Use {{variable_name}} in your message_template. Each recipient’s variables object fills the placeholders.
Template:
Recipient:
{
"phone": "+447111000001",
"variables": {
"first_name": "Sarah",
"day": "Tuesday",
"time": "2pm"
}
}
Sent SMS: Hi Sarah, your appointment is on Tuesday at 2pm.
If a placeholder has no matching variable, it renders as an empty string (no crash). Same pattern as Twilio, SendGrid, and Mailchimp.
From the dashboard
In the dashboard, Outbound → SMS Campaigns → New Campaign, you can add recipients two ways:
- Paste numbers: drop in a list separated by commas, tabs, spaces, or newlines. The form auto-detects phones (E.164 format) and any extra columns become variables.
- Upload CSV: drag-and-drop or browse. The form auto-detects the phone column from headers (
phone, number, mobile, tel, telephone) and treats all other columns as variables.
Either way you get a preview table before sending.
Scheduling
Send later instead of now:
{
"schedule_type": "schedule",
"scheduled_at": "2026-05-20T09:00:00Z"
}
Campaign sits at status: scheduled until the time arrives. You can cancel it before then with DELETE /v1/sms/campaigns/:id.
Tracking delivery
Get campaign status:
curl https://api.nixflex.com/v1/sms/campaigns/smsc_a1b2c3d4 \
-H "Authorization: Bearer KEY_ID:KEY_SECRET"
Response:
{
"campaign_id": "smsc_a1b2c3d4",
"name": "March promotion",
"status": "done",
"total_count": 100,
"delivered_count": 95,
"failed_count": 3,
"pending_count": 2,
"source": "dashboard",
"recipients": [
{
"phone": "+447111000001",
"variables": { "first_name": "Sarah" },
"status": "delivered",
"twilio_sid": "SM1234567890"
}
]
}
How counts are calculated
Counts come from per-recipient status, not from a cached field. Same logic in the dashboard and the API — they always match.
| Count | Calculation |
|---|
delivered_count | Recipients with status delivered |
failed_count | Recipients with status failed, undelivered, or sent |
pending_count | Recipients with status pending or queued |
total_count | All recipients |
Why does sent count as failed? Twilio’s sent status only means Twilio handed the message off to the carrier — not that it reached the handset. Carrier confirmation comes as delivered. We treat anything that did not reach delivered as failed, so what you see in the dashboard matches what really happened.
Campaign status
| Status | Meaning |
|---|
draft | Created but not launched |
scheduled | Will launch at scheduled_at |
running | Currently sending |
done | All recipients processed |
failed | All recipients failed at submission |
Per-recipient status
| Status | Meaning | Shown in UI as |
|---|
pending | Just queued by Nixflex | Pending |
queued | Sent to Twilio API | Pending |
sent | Twilio handed to carrier (no delivery confirmation) | Failed |
delivered | Carrier confirmed delivery to handset | Delivered |
undelivered | Carrier rejected | Failed |
failed | Twilio itself failed | Failed |
List campaigns
curl https://api.nixflex.com/v1/sms/campaigns \
-H "Authorization: Bearer KEY_ID:KEY_SECRET"
Returns all campaigns for your account, newest first. Each one includes computed counts.
Cancel a campaign
curl -X DELETE https://api.nixflex.com/v1/sms/campaigns/smsc_a1b2c3d4 \
-H "Authorization: Bearer KEY_ID:KEY_SECRET"
Cancels a scheduled or running campaign. Already-sent messages are not recalled. A done campaign cannot be cancelled.
Source tracking
Every campaign records where it came from in the source field: "dashboard" or "api". The dashboard shows this as a badge so you can tell at a glance which campaigns came from automation versus manual sends.
Rate limiting
Twilio rate-limits SMS at ~1 message per second per number by default. The engine paces sends to respect this. A 1,000-recipient campaign takes roughly 20 minutes to send fully.
Need faster? Request a higher MPS limit from Twilio support, or add more sending numbers.
Compliance
Bulk SMS has stricter rules than one-off messages.
- UK: All recipients must have opted in. Honour STOP keywords. Keep records of consent.
- US: Requires A2P 10DLC registration. Unregistered traffic is blocked by carriers.
Sending without consent risks carrier blocking, fines, and your sending number being flagged as spam.
Costs
Same per-message Twilio billing as one-off SMS. No platform markup. Budget accordingly for large campaigns.