Skip to content
YourMail

Migration guide

Migrate from Postmark

Postmark and YourMail hold the same position — transactional mail is a different job from campaigns, and suppression is not optional — so the concepts move across cleanly. The work is mostly renaming: PascalCase fields to camelCase, a server token to an API key, message streams to a single bulk flag. If you are still deciding, read YourMail vs Postmark first; it is candid about where Postmark is ahead.

Before you change any code

  1. Verify the domain with the three DKIM CNAME records in Domains. Postmark's records can stay in place until you are done with it.
  2. Import the suppression list, from every stream you send on. See below.
  3. Make a test send following the quickstart.

Then plan the timing. A new account's daily cap ramps up over its first seven days of live sending, counted in recipients, as set out on Limits. Steady transactional traffic rarely meets it; a launch can. Cut over ahead of a launch, not on the morning of one.

The send call

await fetch("https://api.postmarkapp.com/email", {
  method: "POST",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "X-Postmark-Server-Token": process.env.POSTMARK_SERVER_TOKEN,
  },
  body: JSON.stringify({
    From: "Acme <hello@acme.com>",
    To: "customer@example.com, other@example.com",
    Subject: "Reset your password",
    HtmlBody: "<p>Use this link to reset it.</p>",
    TextBody: "Use this link to reset it.",
    ReplyTo: "support@acme.com",
    Tag: "password-reset",
    Metadata: { userId: "u_123" },
    Headers: [{ Name: "X-Entity-Ref-ID", Value: "reset-u_123" }],
    MessageStream: "outbound",
  }),
});

POST https://api.postmarkapp.com/email becomes POST https://api.yourmail.dev/v1/emails, and the response is { "id": "…" }. The SDK is npm i yourmail; it throws a typed error on failure and retries what is safe to retry with an idempotency key attached. Full reference: Send email.

Field by field

Postmark's field names are as of October 2026, from their Email API reference (opens in a new tab).

X-Postmark-Server-Token→ Authorization
A YourMail API key, sent as Authorization: Bearer …. Keys carry a scope: mint a send key for code that only sends, and use a full key for the suppression import below.
From→ from
Must be on a domain you have verified with YourMail. Display names work.
To, Cc, Bcc→ to, cc, bcc
Postmark takes one comma-separated string; YourMail takes a string or an array. The limit is the same: 50 recipients across all three.
Subject→ subject
Same.
HtmlBody, TextBody→ html, text
One or both; 1 MB combined.
ReplyTo→ replyTo
One address. Postmark accepts several, comma-separated; YourMail takes a single string.
Headers→ headers
An array of { Name, Value } becomes a plain object of name to value. Up to 10; headers the pipeline owns are refused with a 400.
Tag→ tags
One string becomes an entry in an array of { name, value }, up to 10 — e.g. { name: "tag", value: "password-reset" }. Tags appear on the message, in search, and in webhook payloads.
Metadata→ tags
Key/value pairs fold into the same tags array. Everything shares the 10-tag cap, so a message with many metadata keys needs trimming.
MessageStream→ bulk
Transactional is the default and needs nothing. Mail you sent on a broadcast stream sets bulk: true — see below.
TrackOpens, TrackLinksnone
No per-message switch. Open and click tracking is a per-domain setting, toggled in the dashboard.
Attachments→ attachments
Name, Content (base64) and ContentType become filename, content and contentType. ContentID has no equivalent. Up to 20 files, 10 MB each, 25 MB total.

Message streams become the bulk flag

Postmark routes mail by stream, and sends on its default outbound transactional stream when none is named. YourMail has no streams. Transactional is the default; anything non-transactional — a newsletter, a digest, an announcement — sets bulk: true:

// Was: MessageStream set to a broadcast stream.
await yourmail.send({
  from: "Acme <news@acme.com>",
  to: subscriber.email, // exactly one recipient, no cc/bcc
  subject: "What shipped in October",
  html: renderNewsletter(subscriber),
  bulk: true, // adds one-click List-Unsubscribe and honours unsubscribes
});

That injects RFC 8058 one-click List-Unsubscribe headers (unless you set your own) and honours unsubscribes. A bulk send must have exactly one to and no cc or bcc. An unsubscribe blocks bulk mail only, so the same person's password reset still sends — the separation streams gave you, kept per message instead.

Batch sends

Postmark's /email/batch takes up to 500 messages per call (as of October 2026, per the reference above). POST /v1/emails/batch takes up to 100, as a bare array or { emails: [...] }, so chunk larger batches. Each item is validated and queued on its own and the response is always a 200 — read summary.failed.

Bring the suppression list

As of October 2026 Postmark exposes suppressions per message stream at GET /message-streams/{stream_id}/suppressions/dump, each entry carrying EmailAddress and a SuppressionReason of HardBounce, SpamComplaint or ManualSuppression — see their Suppressions API (opens in a new tab). Dump every stream you send on and import the addresses with POST /v1/suppressions:

import { YourMail, MAX_SUPPRESSION_IMPORT } from "yourmail";

const yourmail = new YourMail(process.env.YOURMAIL_API_KEY); // full-scope key

// For each stream you send on:
//   GET https://api.postmarkapp.com/message-streams/{stream_id}/suppressions/dump
// collect Suppressions[].EmailAddress into `exported`, then:
for (let i = 0; i < exported.length; i += MAX_SUPPRESSION_IMPORT) {
  await yourmail.createSuppressions(exported.slice(i, i + MAX_SUPPRESSION_IMPORT));
}

One judgement call. Every import is recorded as manual, and manual blocks all mail. That is right for hard bounces and complaints. For an address on a broadcast stream's list because the person unsubscribed from a newsletter, importing it would also stop their receipts — decide per stream which lists to bring. The reasoning is in Bounces and suppression lists.

Webhooks

Re-register your endpoint with YourMail (dashboard, or POST /v1/webhooks) and verify each delivery's yourmail-signature header — an HMAC of the raw body — with the SDK's constructWebhookEvent. Events are named email.delivered, email.bounced, email.complained, email.opened and so on; the payload carries the message's tags, which is where your Postmark Tag and Metadata now live. See Webhooks.

What has no equivalent here

Hosted templates. Postmark sends from stored templates through /email/withTemplate with a TemplateId or TemplateAlias and a TemplateModel (as of October 2026, per their Templates API (opens in a new tab)). YourMail stores no templates. Render the HTML in your own code — react-email components, through the SDK's react option, are one way — and send the result.

Broadcast product. YourMail has no campaigns, audiences or broadcasts. bulk: true makes an individual non-transactional send compliant; it does not manage a list for you.

Per-message tracking switches and inline images by ContentID. Tracking is set per domain in the dashboard; attachments are files.

Common questions

Do I need to re-verify my sending domain?

Yes. Postmark's verification proves your domain to Postmark, not to anyone else. YourMail asks for three DKIM CNAME records, shown when you add the domain in the dashboard or with POST /v1/domains. Nothing in that setup asks you to remove Postmark's records, so both can be verified while you cut over.

Can I keep my Postmark suppression list?

Yes. Postmark exposes its suppressions per message stream, so export each stream you send on and import the addresses with POST /v1/suppressions — up to 100 per request, or createSuppressions() in the SDK — before the first live send. Imports are recorded with the reason manual, which blocks every send, transactional included.

What replaces message streams?

The bulk flag. Transactional mail is the default and needs nothing. Mail that went out on a broadcast stream sets bulk: true, which adds one-click List-Unsubscribe headers and honours unsubscribes. A bulk send must have exactly one recipient, and an unsubscribe blocks bulk mail only, so a password reset to the same address still goes.

Can I run Postmark and YourMail side by side during the cutover?

Yes — move one touchpoint at a time and keep the rest on Postmark until each is seen working. Plan the timing around the new-account ramp: a YourMail account's daily limit rises over its first week of live sending, so cut over ahead of a launch rather than on the morning of it.

Switching from another provider?

Last updated