Migration guide

Migrate from Postmark to Volanea.

Postmark migrations are usually about price at volume ($1.80/1,000 overage adds up) or wanting campaigns and automation alongside transactional. The API swap itself is small.

Undecided? Read the honest Volanea vs Postmark comparison first.

01

Create the project and verify your domain

Sign up (free, no card), add your sending domain, and install the DKIM/SPF records Volanea shows you — fresh records, separate from Postmark's. Keep Postmark live for now: nothing gaps while DNS propagates, and verification is one click once it does.

02

Swap the send call

This is the heart of it — one call replaced by one call. Grab your secret key from project settings first.

before — postmark

import { ServerClient } from "postmark";

const client = new ServerClient(process.env.POSTMARK_SERVER_TOKEN!);

await client.sendEmail({
  From: "shop@yourdomain.com",
  To: "ada@example.com",
  Subject: "Your order shipped",
  HtmlBody: "<p>On its way, Ada.</p>",
});

after — volanea

// npm i @volanea/sdk   (or plain fetch — it's one POST)
import { Volanea } from "@volanea/sdk";

const volanea = new Volanea({ apiKey: process.env.VOLANEA_SECRET_KEY! });

await volanea.send(
  {
    to: "ada@example.com",
    subject: "Your order shipped",
    html: "<p>On its way, {{firstName}}.</p>",
    variables: { firstName: "Ada" },
  },
  { idempotencyKey: "order-4921" }, // retries become safe replays
);
  • Field names go lowercase: From/To/Subject/HtmlBody → from/to/subject/html.
  • Postmark's TemplateModel maps to variables; template content itself lives in Volanea templates with versioning and rollback.
  • Message streams don't exist as a concept — transactional and campaign sends are separated by how they're created, and reputation is watched per project.
03

Bring your contacts

Export your list from Postmark as CSV and import it in the Volanea dashboard (or POST /v1/contacts/import). Columns map by header, imports run as a background job with live progress, and re-runs dedupe by email — a partial import is never a disaster. Unsubscribed contacts keep their status, so nobody who opted out gets mailed by mistake.

04

Re-point your webhooks

Register your endpoint in Volanea (the response includes the whsec_… signing secret), then translate the event names your handler listens for:

PostmarkVolanea
Deliveryemail.delivered
Openemail.opened
Clickemail.clicked
Bounceemail.bounced
SpamComplaintemail.complained

Every delivery is HMAC-signed in the X-Volanea-Signature header and retried with backoff for ~24 hours — the docs include a drop-in verifier.

05

Test, then flip

Send through your test key (sk_test_…) first — messages render and log fully but deliver nothing, so you can assert on the whole pipeline in CI. When the domain is verified and the test sends look right, swap in the live key, watch the first real sends land in the dashboard, and retire the Postmark credentials.

06

Watch for the gotchas

  • Postmark webhook payloads are PascalCase; Volanea events are lowercase JSON with the payload under data — handlers need the field-name pass.
  • Inbound processing: if you use Postmark's inbound parsing, hold that workload — Volanea's inbound (with SMTP) is coming soon.
  • Suppress-before-send: import your Postmark suppression list into Volanea suppressions so historical bounces stay suppressed.

An afternoon, start to finish.