# Campaigns, start to finish

A campaign sends a template approved by Meta, from one of your numbers, to a list of contacts, and routes the replies to whoever you choose (a team, a flow or an AI assistant). This guide runs a whole campaign through the API. Try it all first with the [demo key](/en/developers/test-mode/).

## 1. What you need first

```bash
curl https://api.donetalk.com/v1/c/{company}/channels -H "Authorization: Bearer $DONETALK_KEY"
curl https://api.donetalk.com/v1/c/{company}/templates -H "Authorization: Bearer $DONETALK_KEY"
curl https://api.donetalk.com/v1/c/{company}/teams -H "Authorization: Bearer $DONETALK_KEY"
```

- **Number (`channelId`):** where the message comes from.
- **Template (`templateId`):** an `approved` template of the number's account. Its variables show in the text as `{{1}}`, `{{2}}`.
- **Team (`reply.id`):** who gets the replies.

## 2. Create the draft, with the list

```bash
curl -X POST https://api.donetalk.com/v1/c/{company}/campaigns \
  -H "Authorization: Bearer $DONETALK_KEY" \
  -H "Idempotency-Key: reactivation-2026-10" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reactivation · October",
    "channelId": "…",
    "templateId": "…",
    "variables": [
      { "slot": "body.1", "source": "first_name", "fallback": "there" },
      { "slot": "body.2", "source": "value.coupon", "format": "upper" }
    ],
    "audience": {
      "kind": "contacts",
      "people": [
        { "phone": "+5548999990001", "name": "Ana Souza", "values": { "coupon": "ana10" } },
        { "phone": "+5548999990002", "name": "Bruno Lima", "values": { "coupon": "bru15" } }
      ]
    },
    "reply": { "kind": "team", "id": "…" },
    "startAt": "2026-10-24T12:00:00.000Z"
  }'
```

- **Audience:** `people` carries each person's phone, name and values (up to 20,000; each phone becomes a contact, created or updated, which also needs the `contacts:write` scope). You may also use existing contacts (`contactIds`), a segment (`{"kind": "segment", "segmentId": "…"}`) or a filter.
- **Variables:** each template `{{n}}` comes from `name`, `first_name`, `phone`, a contact field (`field.<key>`), a value from the list (`value.<key>`) or fixed text. `fallback` is used when the value is missing.
- **When:** without `startAt` it starts when sent; with it, it is scheduled. By default it only sends Monday to Friday, 9:00 to 18:00, in the company's time zone (`sendWindow`).

## 3. Check before sending

```bash
curl -X POST https://api.donetalk.com/v1/c/{company}/campaigns/{id}/message-preview -H "Authorization: Bearer $DONETALK_KEY"
curl -X POST https://api.donetalk.com/v1/c/{company}/campaigns/{id}/review -H "Authorization: Bearer $DONETALK_KEY"
```

The preview shows the text for five people and the empty variables. The review gives the estimated cost (up to), the number's daily limit, the budgets and whether approval will be needed. `POST /campaigns/{id}/test` sends the message to your phone.

## 4. Send

```bash
curl -X POST https://api.donetalk.com/v1/c/{company}/campaigns/{id}/submit -H "Authorization: Bearer $DONETALK_KEY"
```

The answer says `running` (started), `scheduled` or `pending_approval` (the company's rule asks for approval, and someone approves it in the app). A key marked "Start campaigns without waiting for approval" does not wait. Whoever got marketing in the last 7 days, opted out or is blocked is left out; the audience preview says how many and why.

## 5. Follow it

Through [webhooks](/en/developers/webhooks/), with no polling: `campaign.started`, `campaign.recipient.sent`, `.delivered`, `.read`, `.replied`, `.failed`, `.opted_out` and `campaign.completed`, with the totals. When the conversation the campaign opened is closed, `conversation.closed` arrives with the outcome and the `campaignId`.

Or by asking:

```bash
curl "https://api.donetalk.com/v1/c/{company}/campaigns/{id}" -H "Authorization: Bearer $DONETALK_KEY"
curl "https://api.donetalk.com/v1/c/{company}/campaigns/{id}/recipients?status=replied" -H "Authorization: Bearer $DONETALK_KEY"
```

## 6. Pause, resume, cancel, retry

`POST /campaigns/{id}/pause`, `/resume`, `/cancel`. Failures worth retrying go back to the queue with `POST /campaigns/{id}/retry` (all, or only some contacts or error codes). A campaign also pauses by itself if Meta pauses the template, the number's quality drops, many people opt out or the budget runs out.
