Campaigns
A campaign sends a marketing template from a verified domain to an audience and produces one message per eligible recipient. POST /campaigns creates either a blast — a one-time send to an audience built from lists, segments and uploaded files — or an automation, a standing journey that contacts enter one at a time (see Automations). Both are created in draft. This page covers blasts and the lifecycle both types share. The third type value, transactional, is refused with 422 VALIDATION_ERROR and validation_error: [{ "field": "type", "message": "NOT_IMPLEMENTED" }]; transactional mail is sent with POST /transactional/send.
Key fields
| Field | Description |
|---|---|
type |
blast or automation. |
status |
See Statuses. |
domain_id |
The sending domain. It must be verified when you schedule or launch the campaign. |
template_id / template_version |
The template must have message_class: marketing (otherwise 422 template_class_mismatch) and be active when you schedule or launch. A blast pins the template’s current version at launch, so later edits do not change what it sends; an automation uses the template’s current version at each send step. |
audience |
Who receives a blast. See Audience. Not used by automations. |
schedule_at |
The planned launch time of a scheduled blast; null otherwise. |
sender_profile_id |
The sender profile whose mailbox is the From address. null uses the route for the campaign’s type (blast or automation) and, without a route, your default profile. An id that is not yours is 422 sender_not_found. |
from_name / reply_to |
Optional overrides of the resolved profile’s display name and Reply-To. The From address itself always comes from a sender profile; the older from_email field is no longer accepted (422). |
throttle_per_hour |
An optional cap on how many recipients per hour the campaign hands to sending. It paces the campaign only; the domain’s warmup allowance and other sending limits still apply. |
recipient_count |
The number of recipients resolved at launch. |
stats |
Counters derived from the current message statuses. See Results. |
pause_reason |
Why a paused campaign was paused. See Statuses. |
launched_at / completed_at |
When sending started and when the campaign finished; null until then. |
Audience
{
"include": [
{ "kind": "list", "id": "<LIST_ID>" },
{ "kind": "segment", "id": "<SEGMENT_ID>" }
],
"exclude": [
{ "kind": "list", "id": "<LIST_ID>" }
]
}
audience.include needs at least one member; exclude is optional. Each member has a kind — list, segment, or upload (a recipient file created with POST /campaigns/uploads) — and an id. At launch the audience is resolved once and frozen:
- All
includemembers are combined and allexcludemembers are removed. - From lists and segments, only contacts in status
subscribedremain;pending,unsubscribed,bounced,complainedand erased contacts are left out. - Every address on the suppression list is removed.
Recipients of an upload are not contacts: the file supplies the addresses and, through its column mapping, per-recipient variables. Uploaded addresses are de-duplicated within the upload and suppressed addresses are removed, but an address that appears both as a contact and in an upload is not de-duplicated between the two. POST /campaigns/{id}/preview returns the resolved recipient_count and up to 5 sample addresses before you launch.
Statuses
| Status | Meaning |
|---|---|
draft |
Being configured. Fully editable with PATCH /campaigns/{id}, and the only status in which DELETE /campaigns/{id} works (it deletes permanently). |
scheduled |
A blast waiting for schedule_at. Only throttle_per_hour can still be changed. |
sending |
A blast is handing its recipients to sending, or an automation is running. Only throttle_per_hour can still be changed. |
paused |
Stopped handing out new recipients, manually or automatically; messages already queued still go out. pause_reason says why: insufficient_credits (the balance ran out), credit_protection (the next batch would cross your protected minimum, see Billing & Credits), template_mismatch (the template was removed or is no longer marketing), sender_not_configured (no sender profile resolves for the campaign), or null for a manual pause. |
sent |
A blast is finished: every recipient was handed to sending and every message reached a final status. Automations never reach sent. |
canceled |
Stopped for good with POST /campaigns/{id}/cancel. |
Lifecycle
draft ──schedule──► scheduled ──(schedule_at reached)──┐
│ │ ▼
└────────send─────────┴──────────send──────────► sending ──(all messages final)──► sent
│ ▲
pause │ │ resume
▼ │
paused
draft / scheduled / sending / paused ──cancel──► canceled
- Schedule.
POST /campaigns/{id}/scheduleworks only on adraftcampaign; any other status is409 invalid_state, so a scheduled campaign cannot be rescheduled.schedule_atmust not be in the past —422 schedule_in_past, with 120 seconds of tolerance for clock skew. The domain must be verified (domain_not_verified) and the template active (template_not_active). - Automatic launch. When
schedule_atarrives the platform launches the campaign with the same checks assend. If the launch fails a check permanently, the campaign returns todraftso you can fix it. - Launch now.
POST /campaigns/{id}/sendlaunches adraftorscheduledcampaign and answers202with the campaign insending. Calling it again on a campaign that is alreadysending,sentorcanceledreturns its current state; apausedcampaign must be resumed instead. For an automation,sendis what starts it. - Pause and resume.
POST /campaigns/{id}/pausemovessendingtopaused.POST /campaigns/{id}/resumemovespausedback tosendingand clearspause_reason; fix the cause first (top up credits, restore the template) or the campaign pauses again. - Completion. A blast moves from
sendingtosentonce no recipients are left to hand out and every message has reached a final status. Messages held back by the warmup allowance keep a blast insendinguntil they are sent or fail. - Cancel.
POST /campaigns/{id}/cancelworks from any status exceptsentandcanceled. Messages that were queued but not yet handed off becomecanceledand their credits are refunded; messages already handed off are not recalled. Canceling an automation also ends its active and waiting enrollments.
Launch checks
A launch (send, or the automatic launch of a scheduled blast) is refused when:
- the campaign is not in a launchable status —
409 invalid_state; - your account is suspended or awaiting review —
409 tenant_suspended,409 tenant_needs_moderation; - the content was rejected, is held for review, or changed after it was approved —
409 campaign_content_rejected,409 campaign_needs_content_review,409 template_changed_since_approval; - the domain is not verified or the template is not an active marketing template —
domain_not_verified,template_not_active,template_class_mismatch; - a recipient’s variables fail the template’s variable rules —
422 invalid_variable; - no recipient would remain after exclusions —
all_recipients_excluded; - the balance cannot fund the send —
402withinsufficient_creditsorcredit_protection; - recipients would receive more than one email that day and you did not choose how to handle it —
409 CONFLICTwith a conflict summary (see below).
On a 409, branch on meta.details.code; a conflict summary has no code.
Same-day conflicts
POST /campaigns/{id}/conflicts reports recipients who would receive more than one email on the campaign’s target day (a calendar day in your account’s time zone), counting mail of any type already sent that day and other scheduled or sending campaigns. The summary gives target_date, audience_size, conflict_count, and the count by source. To launch anyway, pass conflict_resolution to schedule or send:
| Value | Effect |
|---|---|
send_anyway |
Send to everyone as planned. |
exclude_conflicts |
Skip the conflicting recipients. Excluding every recipient is refused. |
delay_1_day |
Send to the conflicting recipients on the next day. |
spread_schedule |
Spread the conflicting recipients over the following days. |
Sending and credits
After launch, recipients are handed to sending in batches, paced by throttle_per_hour when set. Each batch reserves one credit per message before the messages are created; a batch that cannot be funded creates no messages and pauses the campaign with pause_reason: insufficient_credits or credit_protection. Resume it after topping up. See Billing & Credits.
Results
GET /campaigns/{id} returns stats, recalculated from the current status of the campaign’s messages shortly after each change, so the numbers can lag a few seconds behind events:
| Counter | Counts messages that are currently |
|---|---|
sent |
sent or delivered — accepted by the provider and not bounced or complained. |
delivered |
delivered. It can decrease when a delivered message later hard-bounces or is reported as spam. |
bounced |
bounced. |
complained |
complained. |
failed |
failed or suppressed. |
opened / clicked |
with at least one recorded open / click. |
Queued, deferred and canceled messages are not counted. GET /campaigns/{id}/series?range=7d|30d|all returns the same counters per day, with totals for the period.
Related API
- Campaigns tag —
GET /campaigns,POST /campaigns,GET /campaigns/{id},PATCH /campaigns/{id},DELETE /campaigns/{id},GET /campaigns/{id}/series,POST /campaigns/{id}/preview,POST /campaigns/{id}/conflicts,POST /campaigns/{id}/schedule,POST /campaigns/{id}/send,POST /campaigns/{id}/pause,POST /campaigns/{id}/resume,POST /campaigns/{id}/cancel,POST /campaigns/uploads.
See the API Reference for full schemas, and Send your first campaign for a worked example.