Postbatch quick start
A Postbatch is a one-off mailing from a file: a template version, a product finish and destination, one CSV with the recipients and variables, a review of the validation, a quote and an approval that releases every valid item to a production run. The import, the review and the approval are available through the API and through the portal.
1. Create the Postbatch
The template needs a published version; the product follows from the version, the finish and destination must be allowed by the product specification.
const postbatch = await client.postbatches.create({
body: {
name: 'Spring mailing',
templateVersionId: version.id,
finish: 'silk',
destination: 'NL',
},
idempotencyKey: 'crm-campaign-2026-spring',
});The Postbatch starts in DRAFT with empty counts.
2. Upload the file
The CSV goes through an upload session of kind csv (UTF-8 or Windows-1252, the delimiter is detected, a header row is required). Attach the completed upload to the Postbatch; pass the mapping when you know the columns, or leave it out to receive a proposal from the template's mapping preset or the header names.
const upload = await client.uploads.create({
kind: 'csv',
fileName: 'spring.csv',
contentType: 'text/csv',
sizeBytes,
});
await fetch(upload.url, { method: 'PUT', body: bytes, headers: upload.headers });
await client.uploads.complete(upload.id);
const imported = await client.postbatches.startImport(postbatch.id, {
uploadId: upload.id,
mapping: {
name: '{{Naam}}',
addressLine1: '{{Straat}} {{Huisnummer}}',
postalCode: '{{Postcode}}',
city: '{{Plaats}}',
firstName: '{{Voornaam}}',
},
});Every target (the recipient fields and every variable of the version's schema) takes an expression of static text and {{column}} references. The request answers 202 with the import; the Postbatch is IMPORTING. Poll GET /v1/postbatches/{id}/import until the import is MAPPED (a complete mapping), PARSED (a proposal with unmapped required targets; confirm a mapping with PUT .../import/mapping, optionally saved as the template's preset) or FAILED (the errors say why: no header, duplicate headers, no data rows, more rows than the plan allows). A new file for a DRAFT, IMPORTING or VALIDATED Postbatch replaces the earlier import and removes its items.
3. Process the rows
await client.postbatches.processImport(postbatch.id);The rows become items in chunks: validated like Postflow items, deduplicated on the normalised address within the file (duplicates are kept as rows you can inspect with GET .../import/rows?status=DUPLICATE and keep after all with POST .../import/rows/{rowId}/keep), and matched against your suppression list. GET .../import carries the counters (processed, total, valid, needsAttention, suppressed, duplicates); GET .../import/progress streams the same view as server-sent events. When the import is DONE the Postbatch is VALIDATED with its counts per item status and the event postbatch.validated fires.
4. Review
GET /v1/postbatches/{id}/items lists the Postbatch's items with the item list conventions, for instance filter[status]=NEEDS_ATTENTION. Repair an item with PATCH /v1/items/{id}, cancel it with POST /v1/items/{id}/cancel, or skip every item that needs attention at once with POST .../skip-attention. A proof PDF of any item comes from POST .../proof with the itemId; poll the render it answers with.
5. Quote and approve
const quote = await client.postbatches.quote(postbatch.id);
// quantity, unitPriceCents, totalExclVatCents, totalInclVatCents, nextRun, plannedRuns
const approved = await client.postbatches.approve(postbatch.id, {
skipInvalid: true,
targetRunId: quote.plannedRuns[1]?.id,
});The quote prices the valid items on your pricing with the Postbatch quantity as the tier basis and shows the next run with its cutoff and expected delivery. The approval needs the postbatches.approve permission: it pins the template version, locks the quote in a price snapshot, reserves the amount from your balance (or checks your credit on invoice billing), cancels the items that need attention when skipInvalid is set (refused otherwise), and moves every valid item to READY for the next planned run or for a later planned run chosen from plannedRuns. The Postbatch is APPROVED and postbatch.approved fires; it follows its items to SCHEDULED_FOR_RUN, IN_PRODUCTION and COMPLETED (postbatch.scheduled, postbatch.completed). Editors without the approval permission hand over with POST .../request-approval, which notifies the approvers.
6. Cancel
POST /v1/postbatches/{id}/cancel cancels a Postbatch up to APPROVED: the open items are cancelled and their reservations released; items already in production stay. The raw CSV is deleted seven days after the approval or the cancellation.
Events
Subscribe a webhook endpoint to postbatch.* and import.*, filtered on the Postbatch with filters.postbatchId when one integration follows one mailing: import.completed, import.failed, postbatch.validated, postbatch.approved, postbatch.scheduled, postbatch.completed and postbatch.cancelled. The item events of the Postbatch's items (item.*) carry postbatchId.