---
title: "Create a campaign"
method: POST
path: "/v1/campaigns"
tags: ["Campaigns"]
---

# Create a campaign

`POST /v1/campaigns`

## Request

- Base URL: `https://api.mailbeast.ai`
- URL: `https://api.mailbeast.ai/v1/campaigns`
- Auth: HTTP bearer

Create a campaign with its schedule, settings, sequence (with A/B variants), sending accounts, tags, and optionally its leads, in one call. Set `launch: true` to start it right away, which also needs the `campaigns:lifecycle` scope. If it cannot launch, the campaign is still created and `launchError` says why.

Requires one of the following scopes: `campaigns:write`, `campaigns:all`, `all:all`.

## Headers

- `Idempotency-Key` string

## Request body

- CreateCampaignApiDto
  - `name` string, required
  - `schedule` CampaignScheduleApiDto
    - `mode` 'simple' | 'advanced': `simple` — one window (`sendingDays` + `sendingMinutesStart/End`) for all days. `advanced` — per-day windows from `intervals`. Passing `intervals` implies `advanced`.
    - `intervals` ScheduleIntervalApiDto[]: Advanced schedule: sending windows per day (up to 50; windows of one day must not overlap; minutes in 30-minute steps). REPLACES all existing windows. Days without an enabled window do not send.
      - `day` integer, required: Day of week, 1=Monday … 7=Sunday.
      - `startMinutes` integer, required: Window start, minutes from midnight in the campaign timezone (570 = 09:30). Multiple of 30.
      - `endMinutes` integer, required: Window end, minutes from midnight (1050 = 17:30; 1440 = midnight). Multiple of 30, at least 30 minutes after the start.
      - `enabled` boolean: Disabled windows are kept but not used.
    - `timezone` string: IANA timezone. Defaults to UTC.
    - `sendingDays` integer[]: Sending days, 1=Monday … 7=Sunday.
    - `sendingMinutesStart` integer: Send-window start as minutes from midnight (540 = 09:00).
    - `sendingMinutesEnd` integer: Send-window end as minutes from midnight (1020 = 17:00).
    - `dailyLimit` integer: Maximum emails per day for the campaign.
    - `startDate` string, date-time: ISO-8601 start. Omit to start as soon as the campaign is launched (defaults to the creation time).
    - `endDate` string, date-time, nullable: ISO-8601 end: the campaign sends until the end of that day (campaign timezone), then completes. Omit to run indefinitely; on update, `null` removes the end date.
  - `settings` CampaignSettingsApiDto
    - `trackOpens` boolean: Track email opens. Not available with `sendAsTextOnly` (`true` while it is on is a 400).
    - `trackClicks` boolean: Track link clicks. Not available with `sendAsTextOnly` (`true` while it is on is a 400).
    - `sendAsTextOnly` boolean: Send plain-text only (no HTML part). Turning it on turns `sendFirstAsText`, `trackOpens`, `trackClicks` and `unsubscribeLinkBody` off - a plain-text email has no pixel, tracked links or body link.
    - `stopOnReply` boolean: Stop follow-ups to a lead once they reply.
    - `useAllAccounts` boolean: Send from every connected mailbox of the workspace (`true`, the default for a new campaign), or only from the mailboxes in `accounts` (`false` — then `accounts` must hold at least one).
    - `stopOnBounce` boolean: Stop sending to a lead whose email hard-bounced (the address does not exist). Soft bounces do not stop it. Default `true`.
    - `stopCompanyOnReply` boolean: When a lead replies, stop the sequence for every other lead of the same company email domain (someone@acme.com replies → all other @acme.com leads stop). Public domains such as gmail.com or outlook.com are never matched. Default `true`.
    - `highBounceAutoProtection` boolean: Pause the campaign automatically when its hard-bounce rate over the last 24 hours goes above 5%, to protect the sending mailboxes. Default `false`.
    - `skipInvalidEmails` boolean: Skip leads whose email verification came back `invalid` (the address does not exist). Default `true`.
    - `skipUnknownEmails` boolean: Skip leads whose email verification came back `unknown` (the mail server gave no clear answer). Default `true`.
    - `skipRiskyEmails` boolean: Skip leads whose email verification came back `risky` (catch-all / accept-all domain). Worth turning on for large lists: these addresses bounce far more often. Default `false`.
    - `sendFirstAsText` boolean: Send the first step as plain text and the follow-ups as HTML. The first email is then not tracked and has no body unsubscribe link (the `List-Unsubscribe` header still applies). Cannot be combined with `sendAsTextOnly`: turning one on turns the other off (both `true` in one request is a 400). Default `false`.
    - `emailProviderMatching` boolean: Prefer a sending mailbox on the same provider as the lead (Google to Google, Outlook to Outlook) when the campaign has one. Default `false`.
    - `unsubscribeLinkHeader` boolean: Add the one-click `List-Unsubscribe` header (RFC 8058) to every email. Whether the mail client shows an unsubscribe button is up to the provider. Default `false`.
    - `unsubscribeLinkBody` boolean: Add an unsubscribe link at the bottom of the email body. Not available with `sendAsTextOnly` (a plain-text-only email gets no link): turning `sendAsTextOnly` on turns this off, and `true` while it is on is a 400. Default `false`.
    - `autoBlacklistOnUnsubscribe` boolean: Also add a lead who unsubscribes to the workspace blacklist, so no campaign emails them again. Without it, unsubscribing stops only this campaign. Default `false`.
    - `newLeadsRatio` integer: Percent of the daily sending that goes to first emails for new leads; the rest goes to follow-up steps. `0` sends follow-ups only, no new lead is contacted. Default `80`.
    - `abTestingOptimization` boolean: A/B auto-optimization: once every active variant of a step has at least 1000 sends, keep the variant with the best reply rate and switch the others off. Only matters for steps with several `variants`. Default `false`.
    - `keepActiveWhenNoLeads` boolean: Keep the campaign active when it runs out of leads; new leads added later are scheduled automatically. Default false. With `false`, the campaign completes once every lead has received every step. `schedule.endDate` still completes the campaign either way.
    - `hubspotSyncEnabled` boolean: Let the recurring HubSpot sync add new contacts to this campaign. Only matters when HubSpot is connected and a HubSpot segment is imported into this campaign. Default `true`.
  - `sequence` CampaignSequenceStepApiDto[]: Inline email sequence steps, in send order. Omit for no sequence; when supplied it must have at least one step.
    - `subject` string: Email subject. Merge tags (snake_case): {{first_name}}, {{last_name}}, {{full_name}}, {{email}}, {{company_name}}, {{job_title}}, {{website}}, {{linkedin_profile}}, {{location}}, custom fields as {{custom_<field>}}, sender {{senderFirstName}} / {{senderLastName}} / {{senderFullName}}. No spaces inside the braces. Unknown tags render as empty text. Required unless you pass `variants`.
    - `body` string: HTML body. Same merge tags as the subject, e.g. {{first_name}}, {{company_name}}, {{custom_<field>}}. Required unless you pass `variants`.
    - `variants` CampaignSequenceVariantApiDto[]: A/B variants of this step, instead of `subject` + `body`: up to 5, lettered A, B, C… in order. Leads are split evenly between the active ones. Pass either `variants` or `subject` + `body`, not both - a step read from GET also shows variant A as `subject` + `body`; leave those out when sending `variants`.
      - `subject` string, required: Email subject. Merge tags (snake_case): {{first_name}}, {{last_name}}, {{full_name}}, {{email}}, {{company_name}}, {{job_title}}, {{website}}, {{linkedin_profile}}, {{location}}, custom fields as {{custom_<field>}}, sender {{senderFirstName}} / {{senderLastName}} / {{senderFullName}}. No spaces inside the braces. Unknown tags render as empty text.
      - `body` string, required: HTML body. Same merge tags as the subject, e.g. {{first_name}}, {{company_name}}, {{custom_<field>}}.
      - `isActive` boolean: A switched-off variant is kept but not sent. At least one variant of each step must stay on. Omitted = on: when sending variants back, pass `isActive` as the campaign shows it.
    - `delayDays` integer: Days to wait before this step (0 for the first email).
    - `delayHours` integer: Additional hours to wait before this step.
  - `accounts` string[]: SMTP account ids the campaign sends from when `settings.useAllAccounts` is `false`. While `useAllAccounts` is `true` (the default) the campaign sends from every connected mailbox and this list does not limit it.
  - `tags` string[]: Campaign tag ids to put on the campaign. Read the ids from `tags[]` on any campaign; tags themselves are created in the dashboard. Mailbox tag ids are a different dictionary and are rejected.
  - `leads` string[]: Lead emails to add. The response summarizes how many were added. Up to 1000. A malformed address is NOT a request-level 400 - it is counted in the leads summary’s `invalid`, so one bad entry never rejects the create.
  - `launch` boolean: Start sending immediately after the campaign is configured. Requires the `campaigns:lifecycle` scope in addition to `campaigns:write`.

## Response `201`

- CreateCampaignApiResponseDto
  - `campaign` CampaignApiDto, required
    - `id` string, uuid, required
    - `name` string, required
    - `status` 'draft' | 'scheduled' | 'active' | 'paused' | 'stopped' | 'completed' | 'failed', required
    - `schedule` CampaignScheduleView, required
      - `mode` 'simple' | 'advanced', required: Which schedule is in effect. `advanced` → `intervals` decide when it sends and `sendingDays` / `sendingMinutesStart/End` are ignored.
      - `intervals` ScheduleIntervalView[], required: Per-day windows of the advanced schedule (kept even while `mode` is `simple`).
        - `day` number, required: 1=Monday … 7=Sunday.
        - `startMinutes` number, required: Minutes from midnight.
        - `endMinutes` number, required: Minutes from midnight (1440 = midnight).
        - `enabled` boolean, required
      - `timezone` string, required
      - `sendingDays` number[]
      - `sendingMinutesStart` number, required: Minutes from midnight.
      - `sendingMinutesEnd` number, required: Minutes from midnight.
      - `dailyLimit` number, required
      - `startDate` string, date-time, nullable
      - `endDate` string, date-time, nullable
    - `settings` CampaignSettingsView, required
      - `trackOpens` boolean, required
      - `trackClicks` boolean, required
      - `sendAsTextOnly` boolean, required
      - `stopOnReply` boolean, required
      - `useAllAccounts` boolean, required: Sends from every connected mailbox of the workspace (`true`) or only from `accounts` (`false`).
      - `stopOnBounce` boolean, required: Stop sending to a lead whose email hard-bounced.
      - `stopCompanyOnReply` boolean, required: A reply stops the sequence for every lead of the same company email domain.
      - `highBounceAutoProtection` boolean, required: Auto-pause when the 24-hour hard-bounce rate goes above 5%.
      - `skipInvalidEmails` boolean, required: Skip leads whose email verification is `invalid`.
      - `skipUnknownEmails` boolean, required: Skip leads whose email verification is `unknown`.
      - `skipRiskyEmails` boolean, required: Skip leads whose email verification is `risky` (catch-all).
      - `sendFirstAsText` boolean, required: First step as plain text, follow-ups as HTML.
      - `emailProviderMatching` boolean, required: Prefer a sending mailbox on the same provider as the lead.
      - `unsubscribeLinkHeader` boolean, required: One-click `List-Unsubscribe` header (RFC 8058).
      - `unsubscribeLinkBody` boolean, required: Unsubscribe link at the bottom of the email body.
      - `autoBlacklistOnUnsubscribe` boolean, required: Leads who unsubscribe also go to the workspace blacklist.
      - `newLeadsRatio` number, required: Percent of daily sending for first emails to new leads; the rest is follow-ups.
      - `abTestingOptimization` boolean, required: A/B auto-optimization: keep the best-replying variant of each step.
      - `keepActiveWhenNoLeads` boolean, required: true → the campaign stays active when it runs out of leads and schedules new leads as they are added; false → it completes once every lead has received every step.
      - `hubspotSyncEnabled` boolean, required: The recurring HubSpot sync may add contacts to this campaign.
    - `metrics` CampaignMetricsView, required
      - `leadsCount` number, required
      - `sent` number, required: Emails sent.
      - `opens` number, required: Open events.
      - `clicks` number, required: Click events.
      - `replies` number, required: Replies received.
    - `sequence` CampaignSequenceStepView[]: The email sequence steps - included on the detail view (GET /v1/campaigns/:id).
      - `id` string, uuid, required
      - `subject` string, required: Subject of the first variant (a copy of `variants[0].subject`). Leave it out when you send the step back with `variants`.
      - `body` string, required: Body of the first variant (a copy of `variants[0].body`). Leave it out when you send the step back with `variants`.
      - `delayDays` number, required
      - `delayHours` number, required
      - `variants` CampaignSequenceVariantView[], required: A/B variants of this step, by letter (A, B, C…). A step written with `subject` + `body` has a single variant `A`.
        - `id` string, uuid, required: Variant id. Pass it as `variantId` to POST /v1/campaigns/{id}/test-email.
        - `label` string, required: A/B variant letter.
        - `subject` string, required
        - `body` string, required
        - `isActive` boolean, required: Whether this variant is enabled. It is sent only while its step and the campaign are active too. Leads are split evenly between the enabled variants of a step; A/B auto-optimization switches the losing ones off.
    - `accounts` string[]: Attached SMTP account ids - included on the detail view. The campaign sends only from these when `settings.useAllAccounts` is `false`; while it is `true`, from every connected mailbox of the workspace.
    - `tags` TagApiDto[], required: Tags on this campaign - the same ones the list returns, and the same ids `?tags=` filters by. Create and delete them in the dashboard. Campaign tags are a separate dictionary from mailbox tags, so the ids do not cross over.
      - `id` string, uuid, required
      - `name` string, required
      - `color` string, nullable, required: Hex colour shown in the dashboard.
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
  - `leads` CampaignLeadsImportSummaryDto
    - `submitted` number, required: Emails submitted in the request.
    - `created` number, required: New leads created.
    - `duplicates` number, required: Skipped as duplicates.
    - `invalid` number, required: Rejected as invalid/unparseable.
  - `launched` boolean, required: Whether the campaign was launched (true only when `launch: true` succeeded).
  - `launchError` string: Present only when `launch: true` was requested but the campaign could NOT be started. The campaign is still created (as a draft) - fix the issue and start it via the lifecycle endpoint. Absent on success.

## Other responses

- `400`: The request body or parameters failed validation.
- `401`: Missing or invalid API key.
- `403`: The key lacks a required scope, or the plan does not include API access.
- `409`: The request conflicts with current state - e.g. a request with the same Idempotency-Key is still being processed, or the operation partially applied.
- `422`: The Idempotency-Key was already used with a different request body. Use a new key for a new operation.
- `429`: Rate limit exceeded - retry after the interval in the Retry-After header.

---

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