Postmark migration guide

Migrate from Postmark and keep transactional mail isolated

Postscale gives Postmark teams a direct REST and SMTP path while adding bundled inbound rules, masked addresses, DMARC reporting, and EU/EEA-hosted primary service data.

Migration scope

  • Convert PascalCase API fields into Postscale snake_case JSON.
  • Map Message Streams to tags, metadata, and webhook subscriptions.
  • Keep server API token changes isolated behind one mail adapter.
  • Move templates by alias and keep HTML rendering in your app if preferred.

What changes when you move

PostmarkPostscaleMigration note
Email APIPOST /v1/sendMap From, To, Subject, HtmlBody, TextBody, Tag, and Metadata into Postscale fields.
Message StreamsTags and domainsPreserve stream identity as tags or separate sending domains depending on reputation needs.
Inbound webhookInbound Email APIPoint MX at Postscale and keep reply routing identifiers in aliases or metadata.
TemplatesPostscale templatesUse template slugs or UUIDs and pass variables in the send request.
SuppressionsSuppression APIExport per stream and preserve suppression origin during import.

Cut over in controlled steps

1

Mirror DNS

Add Postscale verification, DKIM, return-path, and optional MX records while the old provider keeps sending.

2

Convert code

Swap API credentials and normalize the send payload. Keep the old provider path behind a feature flag for rollback.

3

Move state

Import suppressions and template HTML, then configure delivery, bounce, complaint, and inbound webhooks.

4

Ramp traffic

Start at 10 percent, watch bounces and complaints by ISP, then increase volume as warming limits allow.

Send call conversion

Postmark
await fetch("https://api.postmarkapp.com/email", {
  method: "POST",
  headers: {
    "X-Postmark-Server-Token": process.env.POSTMARK_TOKEN,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    From: "Acme <alerts@example.com>",
    To: "user@example.com",
    Subject: "Welcome",
    HtmlBody: "<p>Hello from Acme</p>",
    Tag: "onboarding",
    Metadata: { user_id: "usr_123" },
  }),
});
Postscale
await fetch("https://api.postscale.io/v1/send", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.POSTSCALE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "Acme <alerts@example.com>",
    to: ["user@example.com"],
    subject: "Welcome",
    html_body: "<p>Hello from Acme</p>",
    tags: ["onboarding"],
    metadata: { user_id: "usr_123" },
  }),
});

Production checklist

Lower DNS TTL to 300 seconds at least 24 hours before cutover.
Keep both DKIM selectors published during the transition.
Import hard bounces, complaints, unsubscribes, and manual suppressions before the first production send.
Run webhook handlers in dual-shape mode until all old-provider events have drained.
Send seed tests to Gmail, Outlook, Yahoo, iCloud, and a corporate Microsoft 365 tenant.
Cut over early in the week and avoid final traffic moves on Friday.
Monitor delivery, bounce, deferred, and complaint rates by ISP after each traffic increase.
Keep the old account active until delayed events and user replies have stopped arriving.

Frequently asked questions

How do Postmark Message Streams map to Postscale?

Most teams preserve stream names as tags and separate reputation-sensitive traffic by domain. High-volume cases can use dedicated domains and warming controls.

Can I keep React Email templates?

Yes. Continue rendering HTML in your app and send html_body, or move reusable templates into Postscale when non-developers need to edit content.

Do inbound replies need a new parser?

No. Postscale parses inbound MIME and sends structured webhooks, so your application can keep focusing on routing and ticket logic.