> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nixflex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SMS campaigns

> Send one-time bulk SMS to many recipients

SMS campaigns send the same message (or templated variations) to many recipients in one API call. Useful for promotions, appointment batches, announcements, and reminders.

<Note>
  **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.
</Note>

## 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](/sms/sms-replies).

## Create a campaign (API)

```bash theme={null}
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:

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```bash theme={null}
curl https://api.nixflex.com/v1/sms/campaigns/smsc_a1b2c3d4 \
  -H "Authorization: Bearer KEY_ID:KEY_SECRET"
```

Response:

```json theme={null}
{
  "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                                            |

<Note>
  **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.
</Note>

## 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

```bash theme={null}
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

```bash theme={null}
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

<Warning>
  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.
</Warning>

## Costs

Same per-message Twilio billing as one-off SMS. No platform markup. Budget accordingly for large campaigns.
