Migrate from Postmark

Moving from Postmark to SendRaven.

Postmark holds no contact list, so there is no audience to import. What moves is the suppression list it built from bounces and spam complaints, the templates, and the send call, plus the parsing of replies if you use an inbound stream.

Message streams map onto SendRaven's risk classes: a transactional stream is transactional mail, a broadcast stream is marketing. The two keep separate sending identities and separate suppression scopes here, which is the same separation Postmark enforces with streams.

Export steps checked 6 September 2026 against postmarkapp.com. Weighing it up? SendRaven vs Postmark.

Gather from Postmark
  1. Message Streams, open each stream, then Suppressions. Export the list as CSV. It carries the address, the reason (hard bounce, spam complaint, manual) and when it was added.
  2. Repeat for every stream you send from. The transactional and broadcast streams keep separate lists.
  3. Templates: open each one and copy the subject, HTML and text bodies out of the editor.

Compared

What changes when you move from Postmark.

What changes the architecture or the billSendRavenPostmark
Priced per email sent, never per contactYes One meter. A contact is free.Yes
Transactional, campaigns and automations on one planYes One key, one plan, one dashboard, one suppression list.Partly broadcast stream; no audiences or automations
The reply is a thread, not a webhookYes Joined on Message-ID to the message it answers.No inbound JSON to a webhook
One query for what is awaiting a replyYes GET /v1/threads?awaiting_reply=trueNo
Per-key daily cap and recipient allowlistYes daily_send_limit and allowed_recipients on the key, checked on every send.No
Human approval hold on a keyYes requires_approval drafts the message and waits for a person.No
Unsubscribe cancels queued sends and ends the sequenceYes Two writes: suppress the address, cancel what is scheduled for it.No not documented

From Postmark's own pricing and documentation, last verified 16 September 2026. A “No” means not offered or not documented on that date. Everything else is on SendRaven vs Postmark.

What moves

What comes across, and what you rebuild.

In PostmarkIn SendRaven
SuppressionsExported per stream. Hard bounces and spam complaints import as bounced and complained; manual suppressions as unsubscribed.
TemplatesMustachio templates are close to ours: {{name}} carries over as it is. Layouts are inlined by hand.
Message streamsA transactional stream sends on the transactional identity; a broadcast stream becomes campaigns on the marketing identity. Separate subdomains, separate reputations.
SendingPOST /email with From, To, Subject, HtmlBody, TextBody becomes POST /v1/emails with from, to, subject, html, text.
InboundThe inbound stream's webhook becomes a domain with an inbound route. Replies join the thread of the message they answer, with quoted text stripped.
Tags and metadataA Postmark tag is a metadata field on the message. Send it as metadata; it comes back on every event and webhook.

The file

What the export looks like, and how the import reads it.

The suppression export has an address column, a suppression reason and an origin. Map the reason column to Subscription status: HardBounce reads as bounced, SpamComplaint as complained, ManualSuppression as unsubscribed. Or import each reason's rows as their own file with one status.

The part that matters

Bring the opt-outs.

Postmark's suppression list is the reason its deliverability record is good, and it does not travel with the API key. An address that hard bounced there hard bounces here; a run of them in the first week is what trips the reputation gate. Import the list per stream before the first send from the new domain.

Suppressions from a broadcast stream are marketing-scoped here: the person opted out of campaigns, not receipts. Hard bounces and complaints from any stream suppress everything.

Templates and sending

The copy and the code.

Mustachio and our template syntax agree on the part that matters: a plain {{variable}} renders the same. Layouts do not carry over as a separate object; paste the layout around each template's body once. Inline CSS stays as it is. Rendering fails rather than sending with a placeholder still visible, and the render endpoint tells you which variables are missing before a real person sees it.

The field names change case and lose their Body suffix: HtmlBody is html, TextBody is text, the X-Postmark-Server-Token header is a Bearer key. Add an Idempotency-Key and a retried request returns the same message rather than sending twice. Batch sends go to POST /v1/emails/batch.

# Postmark
curl -X POST https://api.postmarkapp.com/email \
  -H "X-Postmark-Server-Token: ..." \
  -d '{ "From": "hello@acme.com", "To": "ana@example.com", "Subject": "Hi", "HtmlBody": "<p>Hi</p>", "MessageStream": "outbound" }'

# SendRaven
curl -X POST https://api.sendraven.ai/v1/emails \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: hi-ana-1" \
  -d '{ "from": "Acme <hello@mail.acme.com>", "to": "ana@example.com", "subject": "Hi", "html": "<p>Hi</p>" }'

Then, in order

The steps, in order.

Verify your domain first

Add the domain under Domains and publish the DNS records it gives you. If Postmark had a transactional and a broadcast stream, give each its own subdomain here (mail. and news.), so the two reputations stay separate. Nothing sends until this is green.

Import the suppressions

Contacts → Import a CSV, one file per stream, with the reason column mapped to Subscription status. Each address is suppressed before your first send: bounces and complaints for everything, manual suppressions for marketing.

Move the templates

One stored template per Postmark template, same {{variables}}. Render each once through POST /v1/templates/{slug}/render with real values to catch a missing variable before a send does.

Switch the API key

Point the code at POST /v1/emails with a SendRaven key. Rename the fields, add an Idempotency-Key, and keep the Postmark token alive for a day in case something still points at it.

Move inbound last

Set the inbound route on the domain and repoint the MX record. Replies arrive as threads with the quoted text stripped, and a webhook fires for each. Do this after outbound is stable, because MX changes are the slowest to undo.

Questions

Asked by people leaving Postmark.

Do I lose Postmark's inbound parsing?

You get the equivalent, plus the join: a reply is matched to the message it answers by Message-ID and lands in that thread with the quoted text removed. StrippedTextReply becomes the thread's latest message body. What you do not get is a webhook per inbound address pattern; the route is per domain.

Is there a broadcast stream equivalent?

Campaigns, sent on the marketing identity. They come with audiences, segments and a reputation gate, which Postmark leaves to you. Import the broadcast stream's suppression list first, or the gate has nothing to protect you with.

Start with the domain.

3,000 emails a month, inbound included, and no charge until you switch paid sending on. Verify the domain today. The other guides are on the migrate page.