Email Fast Apply for early access
Migrate

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 — attachment sends are the one exception, refused with a clear error in your provider's own format rather than silently dropped. 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 — a failing single send answers HTTP 422 with the ErrorCode in the body, Postmark's real wire behavior — and /email/batch always returns 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, Cc/Bcc carried on every message fanned from a comma-separated To, and the ErrorCode contract — including the 406 your code already treats as "stop sending to this address." One thing deliberately does not map: outbound attachments aren't supported yet, platform-wide, and a send carrying one answers HTTP 422 with ErrorCode 300 — never silently stripped. 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 moves by hand today

There is no hosted Postmark importer — the planned import lane covers SendGrid, Mailgun, and Mailchimp. Templates re-create in the dashboard or over POST /v1/templates: slug each with your old TemplateId or alias and existing TemplateId/TemplateAlias sends resolve to it unchanged. Suppression lists move by CSV. 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 failing single send answers HTTP 422 with the ErrorCode in the body — 406 for a suppressed recipient, 300 for a blocked send — real Postmark wire behavior, so the official postmark npm client's typed errors work. /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.

See it for yourself

Sandbox keys run the real pipeline dry — real validation, real events, a hosted inbox, no email sent. Early access is onboarding now.