Postmark → Resend
Move Postmark sending to Resend by verifying domains, rewriting the API client, and importing each Message Stream’s suppressions. Postmark servers and streams do not exist in Resend. Mustachio templates do not import. Inbound mail needs a new receive path.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Postmark
- Focused transactional delivery, clear message streams, excellent diagnostics, and support simplify reliable sending
- Audience, campaigns, automations, and reporting live in one publishing system
- Marketing automation and broad multichannel features are intentionally outside its core scope
- Automation logic, analytics history, and deliverability settings are not very portable
Resend
- A simple API, React Email, and clear domain setup make transactional sending approachable for product teams
- Audience, campaigns, automations, and reporting live in one publishing system
- Message streams, native template languages, and long IP history from older providers do not transfer in
- Automation logic, analytics history, and deliverability settings are not very portable
Resend: A simple API, React Email, and clear domain setup make transactional sending approachable for product teams. This removes a major source-side concern: Marketing automation and broad multichannel features are intentionally outside its core scope.
What you lose: Focused transactional delivery, clear message streams, excellent diagnostics, and support simplify reliable sending. What you inherit: Message streams, native template languages, and long IP history from older providers do not transfer in.
Jump to a section
Know the shape of the move.
This timeline assumes
- You send through one or more Postmark Servers and Message Streams, not an inbound-only setup.
- You can add Resend DNS beside Postmark DKIM and Return-Path records until cutover.
- Postmark stays able to send until Resend completes one production cycle.
- The site already documents Postmark → SendGrid and Postmark → Mailgun if Resend is not the destination.
- You export suppressions from every stream before the first Resend production send.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Sender signatures and domains | manual | critical | Postmark needs a confirmed Sender Signature or a verified domain per From address. Resend verifies a domain and then lets you send from any address on it. | Verify the Resend domain. Send a test from each production From address. |
| Servers and Message Streams | lost | critical | Resend’s Postmark guide says Postmark separates transactional and broadcast traffic on streams. Resend does not have streams. All Resend mail shares one sending path. | Split broadcast and transactional traffic with different domains or subdomains if you still need isolation. |
| API authentication and field names | manual | critical | Postmark uses X-Postmark-Server-Token and PascalCase fields such as HtmlBody. Resend uses Authorization: Bearer and snake_case fields. To is a comma-separated string in Postmark and an array in Resend. | Rewrite the client. Do not send PascalCase JSON to Resend. |
| Templates | partial | high | Postmark Templates use Mustachio. They do not become Resend templates. Dub’s Resend customer story rebuilt 12 templates in a weekend with React Email. That is a vendor case study, not a template importer. | Render HTML in the app or rebuild with React Email. |
| Suppressions | partial | critical | Postmark suppressions are per Message Stream. The Suppressions tab exports JSON. The API dump is /message-streams/{stream_id}/suppressions/dump. Resend keeps one team-wide list. | Export every stream. Merge HardBounce, SpamComplaint, and ManualSuppression rows. Import into Resend before live sends. |
| Webhooks | manual | critical | Resend says it adds sent and delivery-delay events that Postmark does not list, and it keeps webhook request logs. Payload names still change. | Map Open, Click, Bounce, Delivery, and SpamComplaint to Resend events. Test one consumer. |
| Inbound email | manual | high | Postmark’s own Resend migration guide says Postmark inbound webhooks include the full body and attachments. It says Resend inbound webhooks send metadata and need extra API calls for content and attachments. Treat that as Postmark’s description of Resend. | Rebuild inbound parsing against Resend’s current receiving docs. Do not assume a drop-in webhook. |
| SMTP | partial | high | Resend’s migrate page says both products support SMTP, with different username and port rules. Postmark’s migrate-from-Resend page says Resend is API-only. Follow Resend’s SMTP docs if you still need SMTP. | Rehearse SMTP on a staging key. Prefer the API if the SMTP path is unclear. |
| Dedicated IPs and warm-up | lost | critical | Stream-scoped IP reputation does not move. | Warm the new path. Keep Postmark for leftover volume if inbox placement slips. |
| Message history retention | lost | medium | Resend’s migrate page says Postmark keeps 45 days of data and Resend keeps 30. | Export any Postmark activity you still need as evidence. |
| Idempotency | manual | medium | Resend documents Idempotency-Key on POST /emails and batch. It says Postmark does not currently support idempotency keys. | Add keys on the new client so retries do not double-send. |
| Rate-limit responses | partial | medium | Resend documents IETF rate-limit headers. It says Postmark errors without those details. | Read the new headers. Do not copy Postmark retry code. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Verified domain or Sender Signature | Resend domain | manual | Verify DNS, then send from any address on that domain. |
| Server API token | Resend API key | manual | Bearer auth. Rotate. |
| Message Stream | Domain, subdomain, or app-level split | transform | Resend has no stream object. |
| HtmlBody / TextBody / To string | html / text / to array | transform | snake_case and arrays. |
| Postmark Template alias | React Email or rendered html | transform | No Mustachio import. |
| Stream suppression dump | Resend team suppression list | transform | Merge every stream. |
| Webhook URL | Resend webhook | manual | Map event names. |
| Inbound stream | Resend receiving, if still required | manual | Payload shape changes. |
| MessageStream parameter | No destination | unsupported | Drop it. Isolate traffic another way. |
Make the move recoverable.
Create the source-of-truth backup
Keep Postmark tokens, stream suppressions, and DNS so you can send again.
- List Servers, streams, From addresses, templates, webhooks, and inbound domains.
- Export each stream’s suppressions from the tab or the dump API.
- Archive Mustachio templates you still need.
- Record DKIM and Return-Path records.
- Keep a Postmark token that can still send.
Proof to capture: A manifest lists every stream, suppression file hash, and token owner.
API rewrite
Replace the Postmark client with Resend’s send shape.
- Switch to Authorization: Bearer.
- Map HtmlBody to html and To to an array.
- Drop MessageStream.
- Add Idempotency-Key on retries.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Per-stream suppression merge
Build one Resend list from every Postmark stream.
- Dump each stream.
- Keep EmailAddress, SuppressionReason, Origin, and CreatedAt.
- Import the union into Resend.
- Test a HardBounce address.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Broadcast and transactional share one Resend path
A newsletter blast uses the same domain as password resets.
- Consequence
- A bad campaign can stain transactional mail.
- Mitigation
- Use a separate subdomain for broadcasts if you used streams for isolation.
Stop if: A marketing send and a login email share an unapproved domain.
One stream’s suppressions never import
You exported outbound but skipped a second stream.
- Consequence
- Resend mails an address Postmark already suppressed.
- Mitigation
- Check every stream ID against the merge file.
Stop if: A production send hits an address present in any Postmark dump.
Inbound parser expects a full Postmark payload
The receiver still reads HtmlBody from the webhook body.
- Consequence
- Inbound tickets or replies stop.
- Mitigation
- Pilot the current Resend receive flow. Keep Postmark inbound until it passes.
Stop if: A required inbound message cannot be reconstructed.
Do the work in this order.
- Days 1–3 · inventory
Inventory Postmark
6–10 hours active2–3 days elapsedOwner review waiting- List servers, streams, templates, webhooks, and inbound.
- Decide how to replace stream isolation.
Depends on: Postmark and DNS admin access
Stop / go checkpointExport?
Go when: Every stream has an owner.
Stop when: A production stream is unknown.
- Days 3–5 · backup
Dump suppressions
4–8 hours active2 days elapsedExports waiting- Export each stream.
- Archive templates and DNS.
Depends on: inventory
Stop / go checkpointBuild Resend?
Go when: Every stream dump hashes.
Stop when: A stream is missing.
- Days 5–12 · pilot
Verify and rewrite
10–22 hours active5–7 days elapsedDNS and inbox tests waiting- Verify the Resend domain.
- Rewrite one send path.
- Import merged suppressions on staging.
- Pilot inbound if you use it.
Depends on: backup
Stop / go checkpointCut over?
Go when: Canary, skip-on-suppression, and inbound pass.
Stop when: A required template still needs Mustachio.
- Days 12–16 · cutover
Switch senders
3–8 hours active1–3 days elapsedQueue drain waiting- Import production suppressions.
- Point the app at Resend.
- Drain Postmark jobs.
Depends on: pilot
Stop / go checkpointResend owns sending?
Go when: No Postmark production send remains.
Stop when: A duplicate or a suppressed send appears.
- Days 16–18 · observe
Watch deliverability
3–6 hours active3–5 days elapsedInbox signals waiting- Watch bounces and complaints.
- Keep Postmark ready.
Depends on: cutover
Stop / go checkpointClose rollback?
Go when: One send cycle is clean.
Stop when: Rates jump or inbound fails.
Cut over with a way back.
Cutover
Make Resend the only production sender after every stream’s suppressions are imported.
- Merge and import the final stream dumps.
- Switch the application to the Resend key.
- Drain Postmark queues.
- Send canaries on transactional and broadcast paths.
- Confirm a known HardBounce is skipped.
Proof to capture: Resend sends production mail. Postmark is idle. Known suppressions are skipped.
Rollback
Return sending to Postmark without losing Resend-era suppressions you still need.
- Stop Resend production sends.
- Restore the Postmark token and DNS.
- Export new Resend suppressions.
- Apply reviewed rows to the matching Postmark streams.
- Send a canary through Postmark.
Proof to capture: Postmark is the only production sender again.
- Suppressed address received mail
- Duplicate send
- Inbound parse failure
- Bounce or complaint spike
- A From address fails verification
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Resend domain verified | Send from each production From address. | Resend accepts the message. | Domain page and received header. | |
V-02Blocking | Every stream merged | Compare stream IDs to the import file, then send to a HardBounce address. | Every dump is represented. Resend skips the address. | Merge sheet and suppression row. | |
V-03Blocking | Field names | Inspect a canary request. | snake_case fields and a to array. No MessageStream. | Request log. | |
V-04Blocking | No Mustachio send | Search for TemplateAlias in production code. | HTML or React Email only. | Code search. | |
V-05Blocking | Event consumer | Trigger delivery and bounce on staging. | One mapped event each. | Consumer log. | |
V-06Blocking | Broadcast domain | Inspect From domains for newsletters vs transactional mail. | Approved split if you used streams for isolation. | From audit. | |
V-07Blocking | Receive path | Send a sample inbound message if inbound is in scope. | Body and attachments arrive through the new flow. | Inbound fixture. | |
V-08Blocking | Postmark still sends | Keep a rollback token and send a staging message. | Postmark still accepts mail. | Rollback send. |
Remove the scaffolding safely.
Safe after: One full send cycle, stable rates, and seven days without a rollback trigger.
- Archive stream dumps and domain screenshots.
- Revoke the Postmark production token.
- Remove Postmark DNS after mail no longer uses it.
- Keep Postmark readable through the retention window.
- Document the Resend key rotation.
- Schedule the next suppression export review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Resend’s Dub customer story says the team moved 12 email templates off Postmark in a weekend and reported no major hiccups. They credit Resend’s docs, React Email, and Tailwind. Dub sends magic links, product alerts, monthly digests, and nurture campaigns. The page is a vendor case study. It does not describe Message Stream isolation, suppression import, or inbound mail.
- Budget a template rewrite in React Email or HTML. Dub did not import Postmark Mustachio templates.
- Still export every Postmark stream’s suppressions. This story never says they skipped that step, and it never says they did it.
- A weekend template port is the claim Resend published for Dub. It is not a suppression or DNS playbook.
- React Email is how they replaced Postmark templates in this vendor writeup.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Resend: migrate from PostmarkAccessed 2026-10-31
- Postmark: can I export a list of all bouncesAccessed 2026-10-31
- Postmark: Suppressions APIAccessed 2026-10-31
- Resend: email suppressionsAccessed 2026-10-31