# Switching from Postmark, ErrorCodes intact

> Switch from Postmark and keep your ErrorCode handling: /email and /email/batch accepted as-is. What ports, what moves by hand, where Postmark still wins.

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

## Keep the semantics you built on

```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
+ });
```

That's the code change, because 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. What the Postmark-compatible
endpoints honor, verified against the code that serves them:

- **`POST /email` and `POST /email/batch`, same shape**, with your Email Fast
  key in the `X-Postmark-Server-Token` header.
- **ErrorCode semantics preserved**: `0` on success with a `MessageID`, **406 for an
  inactive (suppressed) recipient**, 300 for a blocked send. A failing single send
  answers HTTP 422 with the ErrorCode in the body — real wire parity, so the
  official `postmark` npm client's typed errors work against it.
- **`Cc` and `Bcc` carry over** to every message fanned from a comma-separated
  `To`, with native guardrails: per-recipient suppression and quota, the same
  50-recipient cap Postmark enforces, and BCC never appearing in a header.
- **Batch behaves like Postmark's batch**: always HTTP 200, one result per message,
  so a partial batch reads exactly the way your code already handles it — each
  element independently succeeds or fails.
- **Attachments are refused loudly.** Outbound attachments aren't supported yet,
  platform-wide; a send carrying one answers HTTP 422 with ErrorCode 300 and a
  message saying so — never a message quietly delivered without its file.
  `Headers`, `Metadata`, and `InlineCss` are accepted without effect. (Postmark's
  API has no scheduling field, so nothing arrives and nothing is dropped.)
- **Retries can be made idempotent** with an `Idempotency-Key` header, per
  recipient: 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.

And the same admission discipline Postmark users switch providers to keep applies
to every ingress here: 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 — said plainly

There is **no hosted Postmark importer**. The planned import lane covers
SendGrid, Mailgun, and Mailchimp; Postmark templates and layouts are re-created in
the dashboard or over `POST /v1/templates` — slug each with its old `TemplateId`
or alias so existing template sends resolve unchanged. Suppression lists export
from Postmark as CSV and import here as suppressions — do that before your first
send so old bounces and unsubscribes stay honored.

| What you're moving | The work | Time |
|---|---|---|
| Send calls (`postmark` package or raw HTTP) | Request host + token, then a sandbox test | Under an hour |
| Domain verification | SPF/DKIM records at your DNS host | Minutes of work, propagation up to a few hours |
| Templates and layouts | Re-created by hand, slugged with old ids so `TemplateId` sends keep working — no importer for Postmark | An afternoon for a handful; more with heavy layout use |
| Suppression lists | CSV export → suppression import, before first send | Under an hour |

All of it runs dry first: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.

## What Postmark publishes at your volume

Postmark deserves credit for publishing exact calculator ticks rather than
"starting at" prices — the table below is their own published points, not our
interpolation.

| Monthly emails | Price / month | Published as |
| --- | --- | --- |
| 10,000 | $15 | Basic tier |
| 50,000 | $55 | Basic tier |
| 125,000 | $115 | Basic tier |
| 300,000 | $245 | Basic tier |
| 700,000 | $455 | Basic tier |
| 1,500,000 | $775 | Basic tier |

**Detail:** Free developer tier: 100 emails/month, never expires. Calculator slider snaps to fixed ticks only: 10k/50k/125k/300k/700k/1.5M (no 100k or 500k stop) - 100k and 500k figures below use the smallest published tier that covers the volume. Three feature levels at every volume: Basic, Pro (+10%, e.g. $16.50 at 10k), Platform (+20%, $18.00 at 10k). Overage per 1,000 extra: Basic $1.80 ($1.70 at >=300k), Pro $1.30 ($1.25), Platform $1.20 ($1.15). Monthly billing only (no annual plans offered).

**Not published / not verified:** Exact 100k and 500k prices (calculator has no stop at those volumes).

Figures verified 2026-08-29 on [the Postmark (ActiveCampaign) pricing page](https://postmarkapp.com/pricing), promo and intro discounts excluded — method and corrections via the [claims methodology](/legal/claims-methodology).

Price your own volume: [10,000 sends/month](/compare/pricing-calculator?sends=10000&contacts=1000),
[125,000](/compare/pricing-calculator?sends=125000&contacts=10000), or
[700,000](/compare/pricing-calculator?sends=700000&contacts=50000).

## Where Email Fast stands

| Plan | Price / month | Emails included | Overage per 1,000 |
| --- | --- | --- | --- |
| Free | $0 | 2,500 | — |
| Starter | $9.90 | 20,000 | $0.90 |
| Creator | $39 | 100,000 | $0.65 |
| Growth | $79 | 300,000 | $0.55 |
| Scale | $249 | 1,500,000 | $0.35 |

Our stake, declared: we make Email Fast, and these are the launch prices already published on our [pricing page](/pricing) — during early access they may be refined before general availability, and self-serve checkout opens at GA. Contacts and subscribers are never billed, at any list size.

## Where Postmark wins

From our own pricing survey, their side stated plainly — Best-in-class transactional deliverability reputation and delivery speed (separate transactional/broadcast message streams), superb developer experience, and a free tier that never expires.

Honest additions: a transactional reputation earned over many years is not a thing
a new platform can claim to match — ours has to be earned the same way. If Postmark
isn't hurting you on price or missing a feature you need, staying is a defensible
choice. The full comparison: [Postmark vs Email Fast](/compare/postmark-alternative).

## Next

- [The step-by-step migration guide](/migrate/postmark)
- [Also pricing SendGrid](/alternatives/sendgrid) or [Mailgun](/alternatives/mailgun)?
- [Suspended by your current platform?](/alternatives/suspended) Read that first.
- [Apply for early access](/early-access) — live sending starts by application

*Postmark is a trademark of its owner; Email Fast is not affiliated with or endorsed by them. Postmark facts were verified on their public pages on the dates shown — corrections: [hello@emailfast.dev](/contact).*

## Does my Postmark retry and suppression code keep working?

Yes — that's the point of the compatible surface: 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. 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 — which is Postmark's real wire behavior, so the official postmark npm client's typed-error handling keeps working; and /email/batch always answers HTTP 200 with a per-message result array, exactly as Postmark does.

## Do my Postmark templates and layouts come across?

By hand, honestly. There is no hosted Postmark importer — the planned importers cover SendGrid, Mailgun, and Mailchimp — so templates are re-created in the dashboard or over POST /v1/templates . One bridge does work today: give the re-created template a slug equal to your old TemplateId or alias, and existing TemplateId / TemplateAlias sends resolve to it unchanged. Budget an afternoon for a typical set, more if you use layouts heavily.

## Does Email Fast have separate transactional and broadcast streams?

Sends carry a message stream, and suppressions are stream-aware — a newsletter unsubscribe doesn't block a password reset. Postmark's strict stream separation is still the deeper implementation of the idea, and it's listed in their wins on this page for that reason.

## How long does the switch take?

Send calls: under an hour — change the request host, put your Email Fast key in X-Postmark-Server-Token , run a sandbox test. Domain verification adds DNS time. Templates are the manual part: an afternoon for a handful, re-created rather than imported.
