Migrate from Postmark to PostStack
Postmark-grade deliverability on EU infrastructure. €5 instead of $15 to get started.
Postmark is well-respected for transactional deliverability — they were one of the first providers to separate transactional and broadcast traffic into message streams to protect inbox placement. PostStack shares the same hygiene defaults: fast bounce and complaint suppression, one-click unsubscribe, and strict DKIM/SPF/DMARC alignment. The two send APIs are similar in shape; the most visible difference is field casing (Postmark uses PascalCase, PostStack uses lowercase snake_case) and the broader product surface — PostStack includes contacts and segments, IMAP mailboxes, a visual email builder, and DMARC aggregate reports that Postmark does not.
Why switch from Postmark?
- €5/month for 10,000 emails, roughly 62% cheaper than Postmark at that volume
- Savings of roughly 62–76% at every paid volume in the table below
- IMAP and POP3 mailboxes, a visual email builder, and DMARC aggregate reports, none of which Postmark ships
- European infrastructure in Helsinki, Finland, not subject to the US CLOUD Act
Price comparison
Competitor prices are public list prices (USD) verified September 2026; PostStack prices are in EUR. Figures are approximate after currency conversion — check each provider's pricing page for current rates.
| Volume | Postmark | PostStack | Savings |
|---|---|---|---|
| 3,000/mo | ~$15/mo | €0 (free) | 100% |
| 10,000/mo | ~$15/mo | €5/mo | ~62% |
| 50,000/mo | ~$66/mo | €15/mo | ~74% |
| 100,000/mo | ~$126/mo | €30/mo | ~73% |
| 500,000/mo | ~$606/mo | €130/mo | ~75% |
| 1,000,000/mo | ~$1,206/mo | €255/mo | ~76% |
Code migration
Before (Postmark)
import * as postmark from 'postmark';
const client = new postmark.ServerClient(process.env.POSTMARK_API_KEY!);
await client.sendEmail({
From: 'hello@yourdomain.com',
To: 'user@example.com',
Subject: 'Hello!',
HtmlBody: '<h1>Welcome!</h1>',
});After (PostStack)
import { PostStack } from '@poststack.dev/sdk';
const poststack = new PostStack('sk_live_...');
await poststack.emails.send({
from: 'hello@yourdomain.com',
to: ['user@example.com'],
subject: 'Hello!',
html: '<h1>Welcome!</h1>',
});Feature comparison
| Feature | Postmark | PostStack |
|---|---|---|
| REST API | Yes | Yes |
| TypeScript SDK | Yes | Yes |
| Webhooks | Yes | Yes |
| Email Templates | Yes | Yes |
| Broadcasts | Yes | Yes |
| Contact Management | Limited | Yes |
| Open/Click Tracking | Yes | Yes |
| SMTP Relay | Yes | Yes |
| Inbound Email + Webhooks | Yes | Yes |
| IMAP/POP3 Mailboxes | No | Yes |
| Visual Email Builder | No | Yes |
| DMARC Aggregate Reports | No | Yes |
| Workflow Automation | No | Yes |
| Embeddable Signup Forms | No | Yes |
| Contact Segmentation | No | Yes |
| EU Data Residency | No | Yes |
API mapping
Side-by-side reference for the most common operations as you migrate.
| Concept | Postmark | PostStack |
|---|---|---|
| Send transactional email | client.sendEmail({ From, To, Subject, HtmlBody, TextBody }) | poststack.emails.send({ from, to, subject, html, text }) |
| Field casing | PascalCase: From, To, Subject, HtmlBody, TextBody, MessageStream | lowercase: from, to, subject, html, text, reply_to |
| Message streams | MessageStream parameter, "outbound" / "broadcast" / custom | No per-send stream parameter — send marketing from its own subdomain (e.g. news.yourdomain.com) to keep its reputation separate |
| Send with a template | client.sendEmailWithTemplate({ TemplateAlias, TemplateModel }) | poststack.emails.send({ template_id, variables: { ... } }) |
| Bounce webhook signature | IP allowlist (no signature scheme) | X-PostStack-Signature header, HMAC-SHA256 |
| Inbound webhook | POST JSON to your configured URL | `email.inbound` webhook event, JSON (different field names) |
| Suppression management | POST /suppressions/email/{stream} | poststack.suppressions.add({ email, reason }) |
Migration gotchas
Casing flip (PascalCase → lowercase)
Every Postmark payload uses PascalCase (`From`, `HtmlBody`, `MessageStream`). PostStack uses lowercase field names (`from`, `html`, `reply_to`), and `to` is always an array. TypeScript autocomplete catches missed renames; a quick `sed` is enough for plain JS or Python.
Message streams become sending domains
PostStack has no per-send `MessageStream` parameter. Keep transactional mail on your main domain and send marketing from a dedicated subdomain (for example `news.yourdomain.com`), which keeps the two reputations apart at the mailbox providers. Customers with dedicated IPs can also tag each domain’s IP as transactional or marketing.
Postmark webhooks rely on IP allowlists, not signatures
Postmark does not sign webhook payloads — they expect you to allowlist their source IPs. PostStack signs every webhook with HMAC-SHA256. Update your endpoint to verify the `X-PostStack-Signature` header instead of (or in addition to) the IP check.
Template syntax is close, not identical
Postmark templates use Mustachio (`{{variable}}`, `{{#section}}`). PostStack supports `{{variable}}` merge tags and flat `{{#if}}` / `{{#unless}}` blocks, but not loops or nested sections, and not Postmark’s layouts — inline the layout into each template and render repeated content (such as order line items) in your code.
Migration steps
- 1Sign up for PostStack and get your API key
- 2Install the PostStack SDK: npm install @poststack.dev/sdk
- 3Add your domain and verify DNS records
- 4Replace the Postmark SDK with @poststack.dev/sdk
- 5Rename Postmark's PascalCase fields to PostStack's lowercase ones
- 6Test in development, then switch production traffic
Frequently asked questions
Is PostStack’s deliverability comparable to Postmark?
Postmark is a deliverability gold standard for transactional email, and PostStack follows the same principles — immediate suppression of hard bounces and complaints, RFC 8058 one-click unsubscribe, SPF and DKIM checks before a domain can send, DMARC aggregate report ingestion, TLS-RPT, and blocklist monitoring of our sending IPs. Per-provider bounce and complaint rates (Gmail, Outlook, Yahoo, and others) are visible in your dashboard.
Can I keep my Postmark templates?
Mostly. Export the HTML body from Postmark and save it as a PostStack template. Simple `{{variable}}` substitutions work unchanged; Postmark layouts need to be inlined into each template, and loops or nested sections need to be rendered in your code and passed in as variables.
Does PostStack have message streams?
Not as a per-send parameter. The equivalent on PostStack is a separate sending domain: keep transactional mail on your main domain and send broadcasts from a subdomain such as `news.yourdomain.com`, so mailbox providers judge the two streams separately. With dedicated IPs, each domain’s IP can also be tagged as transactional or marketing.
How long does the migration take?
A focused half-day is typical for a small/medium SaaS — most of the time is in updating field names across the codebase. The DNS propagation step finishes in minutes.
What about my Postmark suppressions?
Export them from Postmark and import the CSV in the PostStack dashboard (up to 10,000 addresses at a time), or add them one by one over the API. The list is enforced from the next send.
Can I run both Postmark and PostStack in parallel during the cutover?
Yes — DNS, SPF, and DKIM accommodate multiple providers simultaneously. Route 10% of traffic to PostStack, verify deliverability matches, then ramp.