# Migrate from Postmark

> Migrate from Postmark without a rewrite: keep POST /email and /email/batch, swap X-Postmark-Server-Token for an Email Fast key, keep ErrorCode semantics.

Canonical: https://emailfast.dev/migrate/postmark

## 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

```diff
  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:

```bash
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

```diff
- 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

- [Why teams leave Postmark](/compare/postmark-alternative)
- [The full migration kit](/features/migration)
- [API docs](/docs)

## 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.
