---
title: "Create a campaign"
method: POST
path: "/mailmerge/campaigns"
tags: ["Mail merge"]
---

# Create a campaign

`POST /mailmerge/campaigns`

## Request

- Base URL: `https://api.podpdf.com`
- URL: `https://api.podpdf.com/mailmerge/campaigns`
- Auth: API key in header `X-API-Key`

Parses the recipient list, maps its columns to the template's fields and validates every
row. Returns a draft with a validation report (201), or with `start: true` and consent,
queues it straight away (202). Each email the mail server accepts costs one PDF credit.

## Request body

- CreateMailMergeCampaign: `template_id`, `connection_id` and `email` are required unless `input.retry_of` is used, in which case they default to the first campaign's.
  - `name` string
  - `template_id` string
  - `connection_id` string
  - `input` object, required: Exactly one of `rows`, `csv`, `s3_key` or `retry_of`.
    - `retry_of` string: A finished campaign of yours: the new campaign sends its unsent rows, with its template, sender, mapping and email unless this request gives others. The mapping is used exactly as saved or given.
    - `include` string[]: With `retry_of`, which unsent rows to take. Rows with invalid data are never included.
    - `rows` unknown[]: Objects keyed by column name, or arrays together with `columns`.
      - unknown
    - `columns` string[]
    - `csv` string: A CSV string, up to 5 MB.
    - `s3_key` string: From `POST /mailmerge/uploads`.
  - `email_column` string: Detected from the headers when left out.
  - `mapping` object: Template field key to column name. Unmapped fields are matched by key or label.
  - `email` MailMergeEmail
    - `subject` string, required
    - `body_text` string, required: The plain-text part. `{{tokens}}` name template fields or columns (lowercased, other characters become `_`).
    - `body_html` string, nullable: Optional HTML part, sent as written; filled values are HTML-escaped.
    - `attachment_filename` string, nullable: Defaults to the template name; `.pdf` is added.
    - `list_unsubscribe` string, nullable: A `mailto:` or `https://` target for the List-Unsubscribe header.
    - `list_unsubscribe_one_click` boolean
  - `options` MailMergeOptions
    - `dedupe` boolean: Send once to an address that appears in several rows.
  - `email_notification` boolean: false skips the "campaign finished" email to you.
  - `start` boolean: Queue the campaign now (needs `consent`).
  - `consent` object
    - `confirmed` boolean

## Response `201`

Draft created

- object
  - `campaign` MailMergeCampaign
    - `campaign_id` string
    - `job_id` string
    - `status` 'draft' | 'queued' | 'processing' | 'completed' | 'partial_failed' | 'failed' | 'cancelled'
    - `name` string, nullable
    - `template_id` string
    - `template_name` string
    - `connection_id` string
    - `row_count` integer
    - `valid_row_count` integer
    - `invalid_row_count` integer
    - `duplicate_count` integer
    - `processed_count` integer
    - `sent_count` integer
    - `failed_count` integer
    - `rejected_count` integer
    - `unconfirmed_count` integer
    - `skipped_count` integer
    - `pages_total` integer
    - `report_available` boolean
    - `unsent_available` boolean: Rows that were not sent can be downloaded or sent again.
    - `unsent_count` integer
    - `unsent_summary` MailMergeUnsentSummary, nullable: Rows a finished campaign did not send, by reason. Null when every row was sent.
      - `failed` integer: The email could not be sent, or its PDF could not be made.
      - `not_attempted` integer: The campaign stopped or was cancelled first.
      - `rejected` integer: The mail server refused the address.
      - `unconfirmed` integer: The connection dropped after sending; the email may have arrived.
      - `invalid` integer: Invalid address or missing data. In the CSV only.
    - `outcome_counts` object, nullable: Once finished: how many rows ended each way, keyed by `sent` or the row's error code.
    - `retry_of` string, nullable: The campaign whose unsent rows this one sends.
    - `cancel_requested` boolean
    - `error_code` string, nullable
    - `error_message` string, nullable
    - `created_at` string, date-time
    - `started_at` string, date-time, nullable
    - `completed_at` string, date-time, nullable
    - `email_column` string
    - `mapping` object
    - `email` MailMergeEmail
      - `subject` string, required
      - `body_text` string, required: The plain-text part. `{{tokens}}` name template fields or columns (lowercased, other characters become `_`).
      - `body_html` string, nullable: Optional HTML part, sent as written; filled values are HTML-escaped.
      - `attachment_filename` string, nullable: Defaults to the template name; `.pdf` is added.
      - `list_unsubscribe` string, nullable: A `mailto:` or `https://` target for the List-Unsubscribe header.
      - `list_unsubscribe_one_click` boolean
    - `options` MailMergeOptions
      - `dedupe` boolean: Send once to an address that appears in several rows.
  - `validation` MailMergeValidation
    - `row_count` integer
    - `valid_row_count` integer
    - `invalid_row_count` integer
    - `duplicate_count` integer
    - `columns` string[]
    - `column_keys` object: Column name to the token key email text can use.
    - `email_column` string
    - `mapping` object
    - `suggested_mapping` object
    - `unmapped_required_fields` string[]
    - `unknown_tokens` string[]
    - `invalid_rows` MailMergeRowIssue[]
      - `row_index` integer: 0 is the first data row
      - `row` integer: Spreadsheet row number (the header is row 1)
      - `email` string, nullable
      - `errors` object[]
        - `field` string
        - `column` string
        - `error` string
      - `duplicate_of` integer
    - `duplicates` MailMergeRowIssue[]
      - `row_index` integer: 0 is the first data row
      - `row` integer: Spreadsheet row number (the header is row 1)
      - `email` string, nullable
      - `errors` object[]
        - `field` string
        - `column` string
        - `error` string
      - `duplicate_of` integer

## Other responses

- `202`: Created and queued (`start: true`)
- `400`: The request was invalid
- `401`: Missing or invalid API key
- `402`: `UPGRADE_REQUIRED` — the account has no plan yet
- `403`: `INSUFFICIENT_CREDITS`, `CONVERSION_TYPE_NOT_ENABLED`, `PDF_TOOLS_NOT_ENABLED`, `RATE_LIMIT_EXCEEDED` or `ACCOUNT_NOT_FOUND`. Note that a plan's rate limit is a 403, not a 429.
- `404`: No such resource for this account
- `409`: `CONNECTION_NOT_VERIFIED` or `MAILMERGE_CAMPAIGN_ALREADY_ACTIVE` when starting
- `413`: The request body is over the limit

---

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