# Campaigns (/concepts/campaigns)

A campaign is a single broadcast to an audience you define — think newsletters, product launches, announcements, or promotions. Where a workflow keeps running over time, a campaign fires exactly once (right away or at a scheduled moment) and is immutable afterwards.

## Anatomy of a campaign

Everything required to render and deliver one batch of emails lives on the campaign itself:

import {TypeTable} from 'fumadocs-ui/components/type-table';

<TypeTable
  type={{
  name: { type: 'string', required: true, description: 'Internal label displayed in the dashboard.' },
  description: { type: 'string', description: 'Free-form internal note; optional.' },
  subject: { type: 'string', required: true, description: 'Subject line of the email. Accepts the same template variables as [Templates](/concepts/templates).' },
  body: { type: 'string', required: true, description: 'The HTML body, with template variable support.' },
  from: { type: 'string', required: true, description: 'Sending address — its domain must be verified.' },
  fromName: { type: 'string', description: 'Display name shown as the sender (e.g. `Acme Marketing <hello@acme.com>`). Optional.' },
  replyTo: { type: 'string', description: 'Reply-to address; optional.' },
  type: { type: 'string', required: true, description: 'One of `MARKETING`, `TRANSACTIONAL`, or `HEADLESS` — identical meaning to [template types](/concepts/templates#templates-types). Determines the unsubscribe footer and whether unsubscribed contacts are excluded.' },
  audienceType: { type: 'string', required: true, description: 'Defines the recipients. One of `ALL`, `SEGMENT`, or `FILTERED` — details below.' },
  segmentId: { type: 'string', description: 'Mandatory when `audienceType = SEGMENT`.' },
  audienceCondition: { type: 'object', description: 'Ad-hoc filter used when `audienceType = FILTERED`. Same structure as [segment filters](/guides/segment-filters).' },
  scheduledFor: { type: 'string', description: 'ISO 8601 timestamp. When present, delivery happens at that time; when null, delivery starts as soon as you trigger the send.' },
}}
/>

After sending, the campaign stores denormalised counters — `totalRecipients`, `sentCount`, `deliveredCount`, `openedCount`, `clickedCount`, `bouncedCount` — retrievable through `GET /campaigns/:id/stats`.

## Targeting an audience

There are three ways to define who a campaign reaches:

* **`ALL`** — all subscribed contacts in the project; the fastest route to a full-list broadcast.
* **`SEGMENT`** — the current members of one of your [segments](/concepts/segments), so existing targeting logic can be reused.
* **`FILTERED`** — an inline condition written directly on the campaign (same JSON structure as segment filters). Ideal when you want a bespoke audience for a single send and don't need a saved segment.

Campaigns of type `MARKETING` or `HEADLESS` automatically leave out anyone who has unsubscribed. A `TRANSACTIONAL` campaign, by contrast, reaches every matching contact whether subscribed or not — reserve it for content that is genuinely transactional, and use it rarely.

## Sender domain verification

Bitelio only accepts a `from` address whose domain has been verified for sending; otherwise creating or updating the campaign fails with `403`. See [Verifying domains](/guides/verifying-domains).

## Lifecycle

| Status      | Meaning                                                                                       |
| ----------- | --------------------------------------------------------------------------------------------- |
| `DRAFT`     | Exists but hasn't been scheduled or sent yet; still editable.                                 |
| `SCHEDULED` | Queued for a future `scheduledFor` time; can still be cancelled.                              |
| `SENDING`   | Delivery underway — recipients are being worked through the queue.                            |
| `SENT`      | Delivery finished; the stats won't change anymore.                                            |
| `CANCELLED` | Stopped before finishing. A terminal state reachable from `DRAFT`, `SCHEDULED`, or `SENDING`. |

A campaign in `SENT` or `CANCELLED` can neither be edited nor sent again. To iterate on it, call `POST /campaigns/:id/duplicate` and work on the resulting copy.

## Sending and scheduling

`POST /campaigns/:id/send` kicks off delivery. Sending runs asynchronously in the background — small lists finish within seconds, while very large audiences take longer. The campaign detail page in the dashboard shows live progress.

A `SCHEDULED` or `SENDING` campaign can be stopped with `POST /campaigns/:id/cancel`. Note that cancelling only prevents future sends; any email already accepted by a recipient's mail server is beyond recall.

## Test sends

`POST /campaigns/:id/test` delivers a single preview email. The body takes one address — `{ email: "you@example.com" }` — not a list. Variables resolve exactly as they would in the real send, but delivery goes to the address you supplied, which makes test sends a convenient way to check personalisation using your own contact record.

## Duplicating

Calling `POST /campaigns/:id/duplicate` produces a fresh `DRAFT` that copies the original's content, audience, and settings. Handy for A/B variants or campaigns you send on a recurring basis.

## API reference

The complete list of campaign endpoints — list, get, create, update, delete, send, cancel, duplicate, test, and stats — lives in the [API overview](/api-reference/overview#campaigns).

## What's next

<Cards>
  <Card title="Templates" href="/concepts/templates">
    Build reusable designs to power your campaign content.
  </Card>

  <Card title="Segments" href="/concepts/segments">
    Define the audiences behind `audienceType: SEGMENT`.
  </Card>

  <Card title="Verifying domains" href="/guides/verifying-domains">
    A prerequisite for sending from your own domain.
  </Card>

  <Card title="List hygiene" href="/guides/list-hygiene">
    Protect deliverability by minimising bounces and complaints.
  </Card>
</Cards>
