Skip to content

Campaigns ​

A campaign is one send: a design, an audience, and the printed result — the only object in the API that produces physical output.

One call ​

POST /api/v1/campaigns carries both the content and the recipients, so a whole integration can be one request. Nothing needs to exist beforehand but a template.

POST /api/v1/campaigns
├── content source   (exactly one)   templateId  |  pdf
└── recipient source (exactly one)   listId  |  recipients  |  recipientsFile

Sending zero or two of either is a 400. The pairing is free: any content source works with any recipient source.

Content sources ​

A template you designed and previewed in the web app. The layout is settled; the campaign fills in each recipient's details.

json
{ "name": "Summer promo", "templateId": "550e8400-...", "listId": "550e8400-..." }

The campaign inherits the template's back page, logo, return address and QR configuration. Per-campaign overrides:

FieldEffect
returnAddressReplaces the template's sender text
qrCodeReplaces the template's QR configuration entirely
skipTemplateLogoDo not inherit the logo
skipTemplateReturnLogoDo not inherit the return-address logo
logo, returnAddressLogo (multipart)Supply different images for this campaign

pdf — your own print-ready file ​

Send a pdf part in a multipart request. Your pages become the background; only the postal address blocks are drawn on top.

bash
curl -X POST "$DRSE_BASE_URL/api/v1/campaigns" \
  -H "x-api-key: $DRSE_API_KEY" \
  -F "name=Autumn flyer" \
  -F "pdf=@flyer.pdf" \
  -F "hasBackPage=true" \
  -F "listId=550e8400-e29b-41d4-a716-446655440001"

PDF mode forbids templateId and qrCode — the PDF is the content. hasBackPage prints page 2 on the reverse. Sizing rules are yours to meet: PDF campaigns.

Raw editor content — not supported ​

The endpoint accepts a content field holding a rich-text document. Do not build on it.

Why raw content is excluded

Nothing has rendered it, so nobody has seen what would be printed — and mail cannot be proof-read after printing. A template was approved on screen; a PDF you can open and check.

content stays in the endpoint because the web app uses it. API keys will be rejected for it in a future release. Use templateId, or render a pdf.

Recipient sources ​

SourceList created?Limit
listIdNo — must already exist and be non-empty—
recipientsYes, named after the campaign5000 rows per call
recipientsFileYes, named after the campaign5 MB CSV / 10 MB Excel

Use recipients when your system already holds the addresses, recipientsFile for a spreadsheet export, listId for an audience you send to repeatedly.

An implicit list is a real list — it appears in GET /api/v1/lists and is reusable.

All-or-nothing ​

Every recipient row is validated before any write — before storage uploads, before database inserts.

One bad row means 422 and nothing created: no list, no recipients, no campaign, no print job. Fix the data and retry the whole call.

json
{
  "error": {
    "code": "recipient_validation_failed",
    "message": "Recipient validation failed",
    "details": {
      "issues": [
        { "row": 3, "field": "postTown", "issue": "missing_required", "message": "..." }
      ]
    }
  }
}

So there is never a partial send to reconcile.

Lifecycle ​

pending ──► processing ──► completed
                       └─► failed
StatusMeaning
pendingAccepted; work is being queued
processingDocuments are being rendered and sent to print. Not cancellable
completedEvery document rendered and dispatched
failedProcessing could not finish. Some documents may already have printed

progress runs 0–100. There are no webhooks — poll GET /api/v1/campaigns/:id. See Polling & failures.

failed does not mean nothing was printed

A campaign can fail partway through. Treat failed as "needs a human", not "safe to retry" — a fresh Idempotency-Key could duplicate mail already sent.

The templateId in the response is not yours ​

The templateId a campaign returns is an internal snapshot of the content, frozen at create time.

json
{ "templateId": "018f2c7a-0000-7000-8000-000000000000" }
  • It is not the id you passed in.
  • Fetching it returns Template not found — snapshots are invisible to the API.
  • It exists so that editing your template later cannot alter mail already sent.

Record which of your templates produced a campaign yourself.

Retries and duplicates ​

Sending is not idempotent by default — a timeout you retry can print twice. Always send an Idempotency-Key:

bash
-H "Idempotency-Key: $(uuidgen)"

A retry with the same key returns the original campaign with 200 instead of creating a second. See Idempotency.

Reading campaigns ​

NeedEndpoint
One campaign's statusGET /api/v1/campaigns/:id
Recent campaigns, paginatedGET /api/v1/campaigns
Totals per statusGET /api/v1/campaigns/status-counts

Campaigns cannot be updated or deleted over the API — they are a permanent record of something that physically happened.

Next ​