Migrate from Postmark
Keep your Postmark client and change the host and token, or adopt our SDK. Your ErrorCode handling keeps working, 406 included.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
Two ways to switch
Email Fast speaks Postmark's send API natively: SendGrid-, Mailgun-, and Postmark-compatible endpoints: point your existing SDK at a new base URL with a new key and keep your code. The semantics your retry and suppression code depends on are preserved: ErrorCode: 0 on success, 406 for an inactive (suppressed) recipient, 300 for a blocked send, and /email/batch always returning HTTP 200 with per-message results.
Path 1 — keep the postmark package
import { ServerClient } from "postmark";
- const client = new ServerClient(process.env.POSTMARK_SERVER_TOKEN);
+ const client = new ServerClient(process.env.EMAILFAST_API_KEY, {
+ requestHost: "api.emailfast.dev", // ef_sandbox_… works everywhere
+ });
await client.sendEmail({
From: "you@yourdomain.com",
To: "ada@example.com",
Subject: "Welcome",
HtmlBody: "<h1>Hi Ada</h1>",
});Calling over raw HTTP? Add an Idempotency-Key header and retries become safe:
curl https://api.emailfast.dev/email \
-H "X-Postmark-Server-Token: ef_sandbox_..." \
-H "Idempotency-Key: welcome-ada-1" \
-H "Content-Type: application/json" \
-d '{ "From": "you@yourdomain.com", "To": "ada@example.com",
"Subject": "Welcome", "HtmlBody": "<h1>Hi Ada</h1>" }'Path 2 — adopt @email-fast/nodejs
- await client.sendEmail({
- From: "you@yourdomain.com",
- To: "ada@example.com",
- Subject: "Welcome",
- HtmlBody: "<h1>Hi Ada</h1>",
- });
+ const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY, baseUrl: "https://api.emailfast.dev" });
+ await ef.send({
+ to: "ada@example.com",
+ subject: "Welcome",
+ html: "<h1>Hi Ada</h1>",
+ idempotency_key: "welcome-ada-1",
+ });What maps 1:1
Both endpoints, the server-token header, per-message results on batch, and the ErrorCode contract — including the 406 your code already treats as "stop sending to this address." Every message admitted this way passes the same checkpoint as every other ingress: every send — REST, SMTP, browser SDK, compatibility endpoints, broadcasts, automations — passes through one admission gate: idempotency, suppression, quota, and content policy in a single checkpoint no ingress can skip.
What arms at launch
The hosted data importers — templates, contacts, and suppression lists pulled from your Postmark server — arm at launch. Today you can evaluate the send path end to end with a sandbox key: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.
The idempotency bonus
Pass an Idempotency-Key header and each message in a /email/batch call gets its own derived key, so a timeout-and-retry can never double-send part of a batch: a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.
Next
Questions, answered plainly
Do I need to change my application code to migrate from Postmark?
No. POST /email and POST /email/batch are accepted as-is. Put your Email Fast key in the X-Postmark-Server-Token header and change the host — the response bodies stay Postmark-shaped.
Does my ErrorCode handling still work?
Yes. Success returns ErrorCode: 0 with a MessageID; a suppressed recipient returns ErrorCode 406 (HTTP 406 on /email), a blocked send returns ErrorCode 300. /email/batch always answers HTTP 200 with a per-message result array, exactly as Postmark does.
How do I test before pointing production at Email Fast?
Use a sandbox key (ef_sandbox_…) as the server token. The full pipeline runs — validation, rendering, events, a hosted capture inbox — and no email leaves.