---
title: "Start the send"
method: POST
path: "/api/v1/campaigns/{id}/send"
tags: ["Campaigns"]
---

# Start the send

`POST /api/v1/campaigns/{id}/send`

## Request

- Base URL: `https://mailcheer.com`
- URL: `https://mailcheer.com/api/v1/campaigns/{id}/send`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

Permission required: `campaigns:write`. **Irreversible** (except `dry_run`).

⚠️ **Without a body, the campaign goes to ALL subscribers of its audience with status `subscribed`** (the whole list, or the segment chosen in the interface) — minus those on the suppression list, recomputed at the last moment.

**`to` restricts the send to a list of addresses.** It can only narrow: the letter goes to the requested addresses that are active subscribers of the audience, never to an address on the suppression list. The response carries `audience`: who is kept, who is excluded, and why. An empty list writes to nobody (422); a campaign with an A/B test refuses `to` (the winning version would go out later to the whole audience).

**`dry_run: true` runs every check and queues nothing**: a `200` response with `would_send`, `blocked_reason` and the recipient count. That is the number to show BEFORE confirming. Other fields are ignored, as before — except look-alikes of `dry_run` (`dryRun`, `dry-run`, `test`, `simulate`…) and of `to` (`To`, `recipients`, `emails`, `to_emails`, `destinataires`, `audience`…), refused with 422: ignored, the former would send for real, the latter would send to the whole list.

The call freezes the recipient list and fills the queue. Messages then go out at the rate our provider allows: the response says `sending` and a `queued` count, never `sent`. Follow the progress with `GET /api/v1/campaigns/{id}`.

A campaign already `sending` or `sent` returns 409: we do not re-send, we duplicate. An insufficient monthly quota, a workspace with damaged reputation, no sender, or an empty list return 422 with the reason.

## Path parameters

- `id` string, required

## Request body

- object
  - `to` string[]: Optional. Restricts the send to these addresses — only those that are active subscribers of the audience, never an address on the suppression list. Omitted: the whole audience. Empty (`[]`): nobody, the send is refused.
  - `dry_run` boolean: `true`: check and count everything, queue nothing.

## Response `200`

Dry run (`dry_run`): nothing was queued.

- object
  - `id` string
  - `object` 'send_preview'
  - `dry_run` true
  - `would_send` boolean: Would the real send, started now, go out?
  - `blocked_reason` string, nullable: If not, the exact message the real send would return.
  - `recipients` integer, nullable: How many would be queued; `null` if the refusal comes before counting.
  - `audience` Audience: The breakdown of a send restricted by `to`. The count always adds up: `requested = duplicates + retained + excluded`.
    - `requested` integer: Entries received in `to`, as sent.
    - `duplicates` integer: Entries repeating an address already seen (case and spaces ignored).
    - `retained` integer: Addresses that receive (or would receive) the campaign.
    - `excluded` integer: Excluded addresses.
    - `reasons` object: Excluded addresses per reason; every reason is present, even at zero.
      - `invalid` integer: Not an email address.
      - `not_in_list` integer: No subscriber of the workspace has this address.
      - `pending` integer: Sign-up not confirmed yet (double opt-in).
      - `unsubscribed` integer
      - `bounced` integer
      - `complained` integer
      - `suppressed` integer: Active subscriber, but the address is on the suppression list.
      - `outside_segment` integer: Active subscriber, outside the segment the campaign targets.
    - `excluded_addresses` object[]: Every excluded address, with its reason.
      - `email` string
      - `reason` 'invalid' | 'not_in_list' | 'pending' | 'unsubscribed' | 'bounced' | 'complained' | 'suppressed' | 'outside_segment'

## Other responses

- `202`: Recipients frozen and queued.
- `401`: Key missing, unknown or revoked.
- `402`: The send is refused before anything leaves; nothing was sent or queued. Two causes, told apart by `error.code`: - `quota_exceeded` — the plan's monthly quota does not cover this send. `details` gives the numbers: the plan, its quota, what was sent this month, what is waiting in the queue, what remains, what this call asked for, and when the counter resets. - `free_plan_domain_used` — the workspace is on the free plan, and the sending domain (or another subdomain of the same registered domain) has already used the free plan this month in another workspace. The free plan applies to a sending domain, not to an account. `details`: `domain`, `root_domain`, `period`, `resets_at`. Upgrading to a paid plan lifts the refusal immediately. - `key_quota_exceeded` — the workspace still has room, but THIS key reached its own monthly limit (Settings → API). Another key of the workspace can still send. `details`: `key_id`, `key_name`, `limit`, `used`, `remaining`, `requested`, `resets_at`.
- `403`: The key does not have the requested permission, or the workspace cannot send: `sending_blocked` (suspended, blocked or banned — nothing goes out, through any channel), `commitment_required` (the anti-spam commitment is not accepted), `reputation_blocked` (too many bounces or complaints).
- `404`: The object does not exist in this workspace.
- `409`: Incompatible state, idempotency key reused with a different body, or an identical request still in progress.
- `422`: Malformed field, unverified domain, or recipient on the suppression list.
- `423`: `sending_paused` — the workspace is paused for a safety review. Nothing is wrong with the call: send it again after the decision, unchanged. `Retry-After` gives an interval to try again (300 seconds): while the review lasts, the call returns `423` again, and writes nothing.
- `429`: More than 600 requests per minute for this key (`rate_limit_exceeded` — a batch of N subscribers counts as N), or a new workspace reached its daily limit (`daily_quota_exceeded`: 1,000 emails per rolling 24 hours until it is verified). `Retry-After` always says how many seconds to wait.

---

[API](https://skmtc.dev/mailcheer/apis/mailcheer-api.md) · [All operations](https://skmtc.dev/mailcheer/apis/mailcheer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailcheer/apis/mailcheer-api/revisions/ba64b7ef2170?raw)
