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

# Campaigns

> Create a campaign from your CMS, test it, send it and follow its figures.

The API covers the usual path: your tool writes the content, Lekalao sends it. The finer settings (segment, A/B test, scheduling, a sender of the campaign's own) are made in the interface.

## Create

```bash theme={null}
curl -X POST https://lekalao.example.com/api/v1/campaigns \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "name": "September letter",
    "subject": "The breads of the new season",
    "list": "9d5c2a1e-8f3b-4c7a-9e21-3b8f0c6d4a12",
    "editor": "markdown",
    "content": "# The new season\n\nHello {{ subscriber.first_name }},\n\n…\n\n[Unsubscribe]({{ unsubscribe_url }})  \n{{ organisation.address }}",
    "track_opens": true,
    "track_clicks": true
  }'
```

| Field                         |                                                                          |
| ----------------------------- | ------------------------------------------------------------------------ |
| `name`                        | The internal name.                                                       |
| `subject`                     | The subject; takes [variables](/content/personalization).                |
| `list`                        | The list's `id`.                                                         |
| `editor`                      | `markdown`, `html`, `mjml` or `blocks`. See [Editors](/content/editors). |
| `content`                     | The content, in the editor's format. 500,000 characters at most.         |
| `track_opens`, `track_clicks` | `true` by default.                                                       |

The campaign is created as a **draft**, for the whole list, with the list's sender.

<Tip>
  The content must carry `{{ unsubscribe_url }}` and `{{ organisation.address }}`, directly or through its template. Without them, the campaign will not go out.
</Tip>

## Test

Up to 5 addresses. The subject is prefixed with "\[Test]"; subscribers and statistics are untouched:

```bash theme={null}
curl -X POST https://lekalao.example.com/api/v1/campaigns/$CAMPAIGN/test \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "recipients": ["editor@mybakery.example"] }'
```

## Send

```bash theme={null}
curl -X POST https://lekalao.example.com/api/v1/campaigns/$CAMPAIGN/send \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: september-campaign-send"
```

If everything is ready, sending starts at once:

```json theme={null}
{ "message": "1284 e-mails are on their way.", "queued": 1284 }
```

Otherwise, `422` with the list of what is missing, the same checks as in the interface ([Send](/campaigns/sending)):

```json theme={null}
{
    "message": "This campaign cannot be sent yet.",
    "problems": [
        "Configure a mailer in the settings.",
        "Add your postal address to the content, with {{ organisation.address }}."
    ]
}
```

<Warning>
  Sending cannot be taken back. Use an `Idempotency-Key`: a call repeated
  after a network cut will not send the campaign twice.
</Warning>

## Follow

```bash theme={null}
curl https://lekalao.example.com/api/v1/campaigns/$CAMPAIGN \
  -H "Authorization: Bearer $LEKALAO_TOKEN" -H "Accept: application/json"
```

```json theme={null}
{
    "data": {
        "id": "0c9e3f5a-…",
        "name": "September letter",
        "subject": "The breads of the new season",
        "status": "sent",
        "list": "9d5c2a1e-…",
        "scheduled_at": null,
        "sent_at": "2026-09-17T08:00:12+00:00",
        "statistics": {
            "recipients": 1284,
            "sent": 1284,
            "delivered": 1270,
            "opens": 812,
            "unique_opens": 655,
            "clicks": 190,
            "unique_clicks": 141,
            "bounces": 9,
            "complaints": 0,
            "unsubscribes": 4
        },
        "created_at": "2026-09-16T15:40:02+00:00"
    }
}
```

| `status`    |                                                                         |
| ----------- | ----------------------------------------------------------------------- |
| `draft`     | A draft.                                                                |
| `scheduled` | Scheduled (from the interface).                                         |
| `sending`   | On its way.                                                             |
| `paused`    | Paused, by hand or by [sending safety](/deliverability/sending-safety). |
| `sent`      | Finished.                                                               |
| `cancelled` | Cancelled.                                                              |

The figures are worked out again every minute. `delivered` is filled only when the provider sends its [feedback](/deliverability/feedback). The `campaign.sent` webhook tells you when sending is over.

`GET /api/v1/campaigns?status=sent&list={id}` lists campaigns, the most recent first.

## Delete

`DELETE /api/v1/campaigns/{id}` deletes a campaign and its statistics. `409` while it is sending: pause it in the interface first.

## Templates

So that your CMS only sends the body of the e-mail, prepare a [template](/content/templates) with the header, the footer, the unsubscribe link and the postal address. Templates are also managed through the API: `GET|POST /templates`, `GET|PUT|DELETE /templates/{id}`.
