Skip to content

Campaign from a PDF ​

Send your own print-ready artwork. The platform prints your pages and draws only the postal address blocks on top.

Permissions needed: campaigns:create, campaigns:read. No templates:read — there is no template involved.

Before your first send ​

Page geometry is not validated

The API checks only that the part is application/pdf, is under 20 MB, and really begins with a PDF signature. Page size, page count and orientation are not checked. A PDF that is not A4 is accepted and printed as-is, and you have paid for it.

Confirm, once per layout:

CheckRequirement
Page sizeA4, 210 × 297 mm
Pages1, or 2 with hasBackPage: true
OrientationPortrait
FontsEmbedded
Reserved zonesKept clear (below)
bash
# Page geometry, if you have poppler-utils
pdfinfo flyer.pdf | grep -E "Pages|Page size"
# Page size:      595.276 x 841.89 pts (A4)
# Pages:          2

Keep the top band clear ​

The destination address, return address and postage band are drawn onto page 1. Artwork underneath is printed over, and the mail may be undeliverable.

ZoneFrom top-leftSize
Return address17 mm, 16 mm58 × 17 mm
Postage band121 mm, 32 mm77 × 5 mm
Destination address121 mm, 44 mm77 × 20 mm

The safe rule: treat everything above 77 mm as reserved on the front page, and keep a 5 mm margin on all other edges. The back page needs only the 5 mm margin.

The call ​

Multipart only.

bash
curl -X POST "$DRSE_BASE_URL/api/v1/campaigns" \
  -H "x-api-key: $DRSE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "name=Autumn flyer" \
  -F "pdf=@flyer.pdf;type=application/pdf" \
  -F "hasBackPage=true" \
  -F "listId=550e8400-e29b-41d4-a716-446655440001"
ts
import { readFile } from "node:fs/promises";

const form = new FormData();
form.append("name", "Autumn flyer");
form.append(
  "pdf",
  new File([await readFile("flyer.pdf")], "flyer.pdf", {
    type: "application/pdf",
  }),
);
form.append("hasBackPage", "true");
form.append("listId", "550e8400-e29b-41d4-a716-446655440001");

const res = await fetch(`${BASE}/api/v1/campaigns`, {
  method: "POST",
  headers: {
    "x-api-key": KEY,
    "Idempotency-Key": idempotencyKey,
    // No Content-Type — fetch sets the multipart boundary.
  },
  body: form,
});

const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);

201 Created — qrCode is always null in PDF mode.

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Autumn flyer",
    "status": "pending",
    "progress": 0,
    "listId": "550e8400-e29b-41d4-a716-446655440001",
    "listName": "Summer list",
    "templateId": "018f2c7a-0000-7000-8000-000000000000",
    "publishedAt": null,
    "createdAt": "2026-07-25T10:00:00.000Z",
    "updatedAt": "2026-07-25T10:00:00.000Z",
    "qrCode": null
  }
}

Front and back ​

hasBackPageResult
omitted / falsePage 1 is the front. Extra pages are ignored
truePage 1 front, page 2 reverse

Two pages is the maximum. Sending hasBackPage without a PDF is a 400 hasBackPage only applies to PDF campaigns.

Any recipient source works ​

Combine the PDF with inline recipients:

bash
curl -X POST "$DRSE_BASE_URL/api/v1/campaigns" \
  -H "x-api-key: $DRSE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "name=Autumn flyer" \
  -F "pdf=@flyer.pdf;type=application/pdf" \
  -F 'recipients=[{"name":"Alice Andersson","street":"1 First St","postCode":"111 11","postTown":"Stockholm","country":"SE"}]'

Or with a recipients file — both file parts in one request:

bash
  -F "pdf=@flyer.pdf;type=application/pdf" \
  -F "recipientsFile=@customers.csv;type=text/csv" \
  -F "fileFormat=csv" \
  -F 'mapping={ "countrySource": "fixed", "fixedCountry": "SE", "columnMapping": [ ... ] }'

Remember the whole multipart body is capped at 40 MB — a 20 MB PDF plus a 10 MB Excel file leaves little headroom.

What PDF mode rejects ​

FieldError
templateId400 — A PDF campaign cannot also specify templateId or content — the PDF is the content
content400 — same
backContent400 — backContent is not supported in PDF mode — use hasBackPage to render the PDF's second page
qrCode400 — QR codes are not supported in PDF mode

For a QR code, draw it into the PDF yourself. If you need a tracked code, use a template campaign instead — see QR codes.

No personalisation beyond the address ​

A PDF is the same artwork for every recipient. Only the address blocks vary. For per-recipient content — a name in the greeting, a renewal date — use a template with placeholders: Templates.

Proof before volume ​

There is no dry-run endpoint. Send one campaign to one recipient — your own address — and inspect the letter:

bash
curl -X POST "$DRSE_BASE_URL/api/v1/campaigns" \
  -H "x-api-key: $DRSE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "name=PDF proof" \
  -F "pdf=@flyer.pdf;type=application/pdf" \
  -F 'recipients=[{"name":"Your Name","street":"Your Street 1","postCode":"111 11","postTown":"Stockholm","country":"SE"}]'

Check the delivered mail for: correct scale, nothing clipped at the edges, and no artwork colliding with the address block. Then send the real batch.

Failure modes ​

Statuscode / messageFix
400File must be a PDFThe part's MIME type was not application/pdf
400PDF must be under 20 MBCompress or downsample images
400File is not a valid PDFThe bytes are not a PDF — check the upload
400A conflicting-field messageRemove templateId / content / qrCode
413Request body too large. Maximum 40MB.The whole body, not just the PDF
422list_emptyThe list is empty, or every file row was skipped

Next ​