# Campaigns

> Couryo campaigns send one email to every contact or to a segment by attributes and tags, with scheduling, tests, a review before sending, pause and delivery, open and click stats. No charge per contact.

Source: https://couryo.com/en/docs/campaigns

A **campaign** sends one email to an audience of [contacts](https://couryo.com/en/docs/contacts.md): every subscribed contact of the project, or a segment. It is the list send (newsletter, launch, promotion, notice), always on the `marketing` stream, with one-click unsubscribe and the [suppression list](https://couryo.com/en/docs/suppressions.md) respected.

**No charge per contact.** Campaign emails count in the plan quota and nothing else.

## Who can send

| Plan | Campaigns |
|---|---|
| Free | builds and tests drafts; **does not send** |
| Pro | with the **Automations add-on** (US$ 9 / R$ 49 per month) |
| Scale and Enterprise | included |

You also need [trust level](https://couryo.com/en/docs/limits.md) 1 or more (a verified domain). Without it, sending answers `403 campaigns_not_available`, with `upgrade_options` showing what unlocks it.

## Audience

- **Every subscribed contact** (`{ "type": "all" }`).
- **A segment** (`{ "type": "segment", "match": "all" | "any", "conditions": [...] }`): up to 20 conditions, all of them (AND) or at least one (OR).

| `field` | `op` | Example |
|---|---|---|
| `attribute` (with `key`) | `eq`, `neq`, `contains`, `exists` | `{ "field": "attribute", "key": "plan", "op": "eq", "value": "pro" }` |
| `tag` | `eq` (has the tag), `neq` (does not), `contains`, `exists` (has any) | `{ "field": "tag", "op": "eq", "value": "customer" }` |
| `email` | `eq`, `neq`, `contains`, `exists` | `{ "field": "email", "op": "contains", "value": "@company.com" }` |

- Comparisons ignore case. `neq` also matches contacts without the attribute.
- Anyone who unsubscribed, bounced, complained or was suppressed by hand **is left out on their own**.
- Contact tags come in `tags` on the [contact](https://couryo.com/en/docs/contacts.md).
- `POST /v1/contacts/count` with `{ "audience": ... }` counts the audience without sending: `{ total, subscribed, suppressed }`.
- The audience is **fixed when sending starts**: whoever joins later does not get this campaign.

## Create

```bash title="Create the campaign (draft)"
curl https://api.couryo.com/v1/campaigns \
  -H "Authorization: Bearer $COURYO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: october-campaign" \
  -d '{
    "name": "October collection",
    "from": "Shop <news@shop.com>",
    "audience": { "type": "segment", "match": "all",
      "conditions": [{ "field": "tag", "op": "eq", "value": "customer" }] },
    "subject": "Hi {{name|there}}: the new collection is here",
    "html": "<p>Hi {{name|there}}!</p><p><a href=\"https://shop.com/collection\">See the collection</a></p>"
  }'
```

- **Content:** a [saved template](https://couryo.com/en/docs/templates.md) (`template_id`) or a subject with your own `html` and/or `text`, made in the [editor](https://couryo.com/en/docs/template-editor.md). With a template, `subject` (when sent) replaces the template subject.
- **Variables:** the contact attributes (`{{name}}`, also `{{contact.name}}`), `{{email}}`, the fixed values in `variables` and `{{unsubscribe_url}}`. Use a fallback for contacts without the attribute: `{{name|there}}`. Without a value and without a fallback, the passage comes out empty; an empty subject for one contact is an error for that contact only.
- **Unsubscribe:** the unsubscribe link is added at the end of the email when the content has none, and the one-click `List-Unsubscribe` (RFC 8058) always goes.
- `PATCH /v1/campaigns/{id}` changes drafts and scheduled campaigns. On a paused campaign, the content changes for who is left, but the audience stays fixed.

## Before sending

`POST /v1/campaigns/{id}/preflight` sends nothing and returns:

- `recipients`: the **real** recipient count right now; `suppressed`: how many are left out;
- `never_emailed` and `never_emailed_share`: who never got an email from this project. Above 30%, the `never_emailed` warning reminds you that a cold list hurts delivery;
- `needs_review`: whether it goes to review (below);
- `review`: the free [AI review](https://couryo.com/en/docs/ai.md) of the content;
- `warnings` and `can_send`: what stops sending (plan, level, sender, domain, content, empty audience).

`POST /v1/campaigns/{id}/test` sends the real content, with `[Test]` in the subject, to up to 5 addresses **of the people in your account** (`{ "to": ["you@shop.com"] }`). Tests are not counted in the stats.

## Send, schedule, pause

| Call | What it does |
|---|---|
| `POST /v1/campaigns/{id}/send` | starts now |
| `POST /v1/campaigns/{id}/schedule` with `{ "scheduled_at" }` | starts at the set time (up to 90 days) |
| `POST /v1/campaigns/{id}/pause` | stops between batches; works on scheduled ones too |
| `POST /v1/campaigns/{id}/resume` | continues where it stopped (plan and level checked again) |
| `POST /v1/campaigns/{id}/cancel` | who has not received it will not; what went out stays in the stats |

- Sending goes out **in batches**, at the engine's pace and within your [level limits](https://couryo.com/en/docs/limits.md). When the daily limit runs out, the campaign waits and continues on its own; `status_message` explains.
- Each contact gets it **once**, even if the server restarts in the middle of a batch.
- Statuses: `draft`, `scheduled`, `in_review`, `sending`, `paused`, `sent`, `cancelled`. A paused one has `paused_reason`: `manual`, `bounce_rate`, `limit`, `domain`, `plan` or `content`.

## Safeguards

- **First large campaign:** the account's first campaign above **5,000 recipients** gets a review before it starts (`status: in_review`). The agent releases clear cases right away (level 2 or more, a list with sending history, clean rates); the others get a human look, usually within 1 business day. The answer comes by email. After that, the next ones go straight through.
- **Automatic pause on bounces:** if more than **5% of the first thousand** emails bounce, the campaign pauses on its own (`paused_reason: bounce_rate`) and you get an email with the numbers. The bounced addresses are already suppressed; clean the list before resuming.
- The [account rules](https://couryo.com/en/docs/limits.md) still apply on top: gradual descent, marketing pause and review.

## Stats

`GET /v1/campaigns/{id}/stats`:

```json title="Response"
{
  "campaign_id": "cmp_8k2m4q9w1x7z3abc",
  "recipients": 1203, "sent": 1198, "delivered": 1180, "bounced": 18, "complained": 1,
  "opened": 484, "opened_machine": 170, "clicked": 79, "unsubscribed": 5, "skipped": 5, "failed": 0,
  "rates": { "delivery": 0.985, "bounce": 0.015, "complaint": 0.0008, "open": 0.4102, "click": 0.0669, "click_to_open": 0.1632, "unsubscribe": 0.0042 },
  "top_links": [{ "url": "https://shop.com/collection", "clicks": 66, "total": 91 }],
  "opens_estimated": true
}
```

- **Opens are estimated** (see [Open and click tracking](https://couryo.com/en/docs/tracking.md)). `opened_machine` counts the ones opened only by the mail program's privacy protection.
- `clicks` in `top_links` are people (unique emails); `total` is every click.
- Every campaign email carries the `campaign` tag with the id, so it shows in `GET /v1/emails?tag=campaign:cmp_...` and in [webhooks](https://couryo.com/en/docs/webhooks.md).

## In the dashboard and for agents

In the dashboard, **Campaigns** builds it in steps (audience, content, review, send) and shows the campaign page with the stats. On [MCP](https://couryo.com/en/docs/mcp.md), the tools `create_campaign`, `list_campaigns`, `get_campaign_stats`, `send_campaign` and `pause_campaign`; `send_campaign` only sends with `confirm: true` (without it, it returns the checks and the count).
