Switching from Postmark? Move your transactional email to Envello
A step-by-step guide: DNS setup alongside your existing records, the sending-code swap, a dual-send cutover window, and verification with 90 days of searchable logs on Pro.
Postmark's API uses PascalCase JSON fields and a server token header; Envello uses lowercase fields and a Bearer token. The send call is a mechanical rename. Server-side templates with logic and Message Streams are the two parts that need a decision.
import { ServerClient } from "postmark";
const client = new ServerClient(process.env.POSTMARK_SERVER_TOKEN);
await client.sendEmail({
From: "Acme <[email protected]>",
To: "[email protected]",
Subject: "Welcome",
HtmlBody: renderedHtml,
MessageStream: "outbound",
});import { Envello } from "envello";
const envello = new Envello(process.env.ENVELLO_API_KEY);
await envello.emails.send({
from: "Acme <[email protected]>",
to: "[email protected]",
subject: "Welcome",
html: renderedHtml,
});curl -X POST https://api.postmarkapp.com/email \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
-d '{
"From": "Acme <[email protected]>",
"To": "[email protected]",
"Subject": "Welcome",
"HtmlBody": "<p>Welcome aboard</p>"
}'curl -X POST https://api.envello.dev/emails \
-H "Authorization: Bearer $ENVELLO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <[email protected]>",
"to": "[email protected]",
"subject": "Welcome",
"html": "<p>Welcome aboard</p>"
}'Postmark fields and their Envello equivalents
| Postmark | Envello |
|---|---|
| X-Postmark-Server-Token header | Authorization: Bearer <API key> |
| From, To, Subject | from, to, subject |
| HtmlBody, TextBody | html, text |
| Cc, Bcc (comma-separated) | cc, bcc (string or array) |
| ReplyTo | reply_to |
| Headers: [{ Name, Value }] | headers: { name: value } |
| Attachments: [{ Name, Content, ContentType }] | attachments: [{ filename, content, contentType }] |
| POST /email/batch | POST /emails/batch, up to 100 emails |
| TemplateId / TemplateModel (/email/withTemplate) | Render the HTML in your code, then send it as html |
| MessageStream | No equivalent; Envello is for transactional mail only |
| Tag, Metadata | No equivalent yet |
Templates
Postmark templates support Mustachio logic such as loops and conditionals. Envello's stored templates do {{variable}} substitution only. Simple templates port as they are. Anything with loops or conditionals is easier to render in your own code (React Email, MJML, any engine) and send as html.
Message Streams
Streams separate transactional from broadcast mail inside one Postmark account. Envello has no streams because it doesn't send broadcast mail: keep marketing email in a marketing tool and use Envello for transactional sends. If you need reputation isolation beyond that, dedicated IPs are a paid add-on.
Webhooks and inbound
Postmark bounce webhooks carry its own bounce-type names. Envello sends five event types (delivered, bounced, complained, opened, clicked), so map each Postmark type you branch on to one of those before writing the new handler. Inbound parsing is a separate feature: check what you use before assuming outbound migration covers it.
The migration, end to end
1. Add Envello DNS records alongside your existing Postmark records
Verify your sending domain in Envello and add the SPF include, DKIM CNAMEs, and the return-path CNAME Envello generates. Keep your existing records in place: both providers can authenticate the same domain at once, which is what makes a safe cutover possible. Only remove the old records after Envello has been the sole sender for a few days.
2. Swap the sending call
Point your outbound path at Envello's SDK. The request shape (from, to, subject, html, text, cc, bcc, reply_to, headers, attachments) mirrors the common transactional-API pattern, so the diff in most codebases is the import line, the client instantiation, and the API key environment variable. Templates rendered client-side (React Email, MJML, plain string) plug straight into the html field.
3. Dual-send during the cutover window
Route a small percentage of traffic to Envello first, or fork production sends so each message goes through both providers to non-critical recipients while you verify. Envello's log search shows every send, bounce, complaint, open, and click for 90 days on Pro (7 days on Free, up to 365 days on Scale) so you can confirm parity before flipping the rest of the traffic.
4. Cut over and retire Postmark
Once Envello has handled a full week of production traffic without regressions on your key deliverability metrics (bounce rate, complaint rate, delivered rate per domain), route 100% of sends through Envello. Export your Postmark suppression list and add each address with Envello's POST /suppressions so previously-suppressed recipients stay suppressed. Remove the old DNS records last.
Sending HTML from React components? See the React Email guide for how render() plugs straight into the html field.
Migration questions
Can I keep sending through Postmark while I test Envello?+
Yes. Both providers authenticate the same domain via separate DKIM selectors and their own SPF include, so parallel sending is safe as long as each provider's records are in place. Keep the old records until Envello has been the sole production sender for a few days, then remove them.
What happens to my suppression list?+
Export it from Postmark and add each address with POST /suppressions (a full-access API key works, one address per request), or one by one in the dashboard. If you skip this step, addresses that Postmark had marked as bounced or complained can send successfully through Envello on day one and generate the same bounce or complaint again, which harms deliverability. Import first, cut over second.
Do I need to change my webhook consumer?+
Yes, at least the payload shape. Envello's webhooks send delivered, bounced, complained, opened, and clicked events with HMAC-signed payloads; the field names and signature header are documented at envello.dev/docs. If your current consumer branches on your existing provider's specific bounce-subtype taxonomy, decide upfront how each subtype maps to Envello's simpler event set before writing the new handler.
How long does the whole migration take?+
For a codebase with a single central sending path, most teams complete the code change in under an hour. Add DNS propagation (up to 48 hours, usually much less), a dual-send verification window (a few days), and one week of production monitoring before retiring the old provider. Plan for two to three weeks end-to-end, not one afternoon.
Check your own GDPR/DPA gaps before you switch
8 questions covering data residency, retention, and subprocessor documentation, the same things a security questionnaire asks.
Run the free checklist →