Migrate from Postmark

Migrating from Postmark to Sendpository

Everything that changes when you move your email from Postmark, in the order you'll do it. An hour or two - the request maps field for field.

In short

Migrating from Postmark to Sendpository is a field rename and an auth change: PascalCase fields (From, To, HtmlBody, TextBody) become lowercase ones (from, to, html, text), and the X-Postmark-Server-Token header becomes a Bearer key. Sendpository also signs every webhook with HMAC-SHA256, where Postmark relies on basic auth or IP allowlisting.

Or let your coding agent do the whole migration

Paste it in. It finds every Postmark call, rewrites it, updates your webhook handler and switches over behind a flag you can flip back.

Show the prompt

Switch this project's email from Postmark to Sendpository. First run `npx sendpository@latest init` - it opens my browser for me to approve, saves the API key and installs the Sendpository skill - then follow the skill's migration guide (references/migrate.md). If you can't run commands, read https://www.sendpository.com/agents/sendpository/references/migrate.md instead.

1. API differences

PostmarkSendpository
EndpointPOST https://api.postmarkapp.com/emailPOST https://api.sendpository.com/v1/emails
FieldsFrom, To (comma-separated string), Cc, Bcc, Subject, HtmlBody, TextBody, ReplyTofrom, to (array), cc, bcc, subject, html, text, reply_to
MetadataTag (one string) and Metadata (key/values)tags: a string key/value object
TrackingTrackOpens, TrackLinkstrack_opens, track_clicks - or an account default
StreamsMessageStream separates transactional and broadcastNo streams - use tags to tell mail apart
BatchPOST /email/batch, up to 500POST https://api.sendpository.com/v1/emails/batch, up to 100
Response{ To, SubmittedAt, MessageID, ErrorCode, Message }{ "id": "..." } on success; an error object otherwise

2. Authentication differences

PostmarkSendpository
API keyX-Postmark-Server-Token: <server token>Authorization: Bearer sp_...

Create the key under API keys in the dashboard. It is shown once, so put it straight into SENDPOSITORY_API_KEY in your secrets manager. Keep the Postmark key until the switch is finished, then revoke it.

3. Domain setup

Add your sending domain in Sendpository and publish the records it shows: a 2048-bit DKIM key signed as your own domain, plus SPF and a bounce MX on a sp. subdomain so your existing records are never edited. DMARC is recommended; if you already publish one, leave it as it is.

  • Postmark verifies a DKIM record and a Return-Path CNAME. Sendpository's DKIM uses a different selector, and its bounce record lives on a sp. subdomain, so both can coexist.

Not sure what’s published already? Run the domain through the email DNS checker.

4. Webhooks

PostmarkSendpository
VerificationNo HMAC signature - secured with basic auth in the URL or an IP allowlistEvery request signed: Sendpository-Signature: t=<unix>,v1=<HMAC-SHA256>. Verify it and you can drop the basic-auth credentials.

Event names

Postmark eventSendpository event
Deliveryemail.delivered
Bounceemail.bounced
Spam Complaintemail.complained
Open Trackingemail.opened
Clickemail.clicked
Subscription Changeemail.unsubscribed
Inboundemail.received

Full payloads and verification code are in the webhooks guide.

5. Error handling

PostmarkSendpository
Error shapeHTTP 422 with a numeric ErrorCode and a Message, e.g. 406 for an inactive recipient{ "error": { "type": "domain_not_verified", "message": "..." } }

Swap numeric ErrorCode checks for error.type strings: an inactive recipient is suppressed_recipient, an unverified sender is domain_not_verified. Only rate_limit_exceeded (wait for Retry-After) and internal_error are worth retrying automatically; the errors reference lists every type.

6. Code migration

Before - Postmark (Node.js, fetch)
await fetch("https://api.postmarkapp.com/email", {
  method: "POST",
  headers: {
    "X-Postmark-Server-Token": process.env.POSTMARK_SERVER_TOKEN,
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify({
    From: "Acme <billing@yourdomain.com>",
    To: "customer@example.com",
    Subject: "Your receipt",
    HtmlBody: "<p>Thanks for your order.</p>",
    TextBody: "Thanks for your order.",
    Tag: "receipt",
    MessageStream: "outbound",
  }),
});
After - Sendpository (Node.js, fetch)
const res = await fetch("https://api.sendpository.com/v1/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SENDPOSITORY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "receipt-10428",
  },
  body: JSON.stringify({
    from: "Acme <billing@mail.yourdomain.com>",
    to: ["customer@example.com"],
    subject: "Your receipt",
    html: "<p>Thanks for your order.</p>",
    text: "Thanks for your order.",
    tags: { type: "receipt" },
  }),
});

const body = await res.json();
if (!res.ok) throw new Error(`${body.error.type}: ${body.error.message}`);
console.log(body.id);

Watch out for

7. Pricing

PostmarkSendpository
Published pricingFree developer plan: 100 emails a month, no expiry. Basic is $15 a month for 10,000 emails, with overage at $1.80 per 1,000.6,000 emails a month (up to 200 a day), free forever. Current plans

Postmark pricing from their own pages, checked 2026-09-23. Sendpository's free plan is larger: 6,000 emails a month (up to 200 a day), against Postmark's 100 a month.

8. Things to test before switching

Specific to leaving Postmark:

True of any migration:

Questions

Does Sendpository have message streams like Postmark?

No. Tag messages instead - tags: { "stream": "broadcast" } - and filter by tag in the dashboard. Sendpository is built for transactional mail; bulk marketing belongs on a marketing platform either way.

Are Sendpository webhooks signed?

Yes. Every webhook carries a Sendpository-Signature header with an HMAC-SHA256 of the timestamp and raw body, so you can reject forged and replayed requests without relying on an IP allowlist.

Postmark API details are from their documentation, checked 2026-09-25: postmarkapp.com, postmarkapp.com.

Keep reading

Send your first email in minutes

6,000 emails a month (up to 200 a day), free forever. No card needed.