Migration

Migrate from Postmark

Postmark and eusend have the same shape — an HTTP API for transactional email, an SMTP door for everything else, and webhooks for delivery events — so a typical migration is an afternoon, most of it waiting on DNS. What you gain is an EU-native provider: your mail is sent and stored on infrastructure in the EU, operated by an EU company, with no US parent in the chain.

The code change

Install the SDK with npm install @eusend_dev/sdk. The main difference you'll notice is casing — Postmark's fields are PascalCase, eusend's are camelCase — and that eusend returns { data, error } instead of throwing on failure.

Before · Postmark
import postmark from 'postmark'

const client = new postmark.ServerClient(
  process.env.POSTMARK_SERVER_TOKEN
)

await client.sendEmail({
  From: 'Acme <hello@acme.com>',
  To: 'user@example.com',
  Subject: 'Welcome',
  HtmlBody: '<p>Thanks for signing up.</p>',
  MessageStream: 'outbound',
})
After · eusend
import { Eusend } from '@eusend_dev/sdk'

const eusend = new Eusend(
  process.env.EUSEND_API_KEY
)

await eusend.emails.send({
  from: 'Acme <hello@acme.com>',
  to: 'user@example.com',
  subject: 'Welcome',
  html: '<p>Thanks for signing up.</p>',
})

Field mapping

PostmarkeusendNotes
FromfromMust use a domain you verified in eusend
To / Cc / Bccto / cc / bccPostmark takes comma-separated strings; eusend takes a string or an array
ReplyToreplyToString or array
Subjectsubject
HtmlBody / TextBodyhtml / text
Headers ([{ Name, Value }])headersA plain object instead of an array of name/value pairs
TrackOpens / TrackLinkstrackOpens / trackClicksBoth default to true on eusend
MessageStreamNo equivalent needed. Transactional sends go through the API; campaigns are a separate Broadcasts feature.
Tag / MetadataNo direct equivalent yet. Custom headers are the usual workaround.

Batch sending maps one-to-one: client.sendEmailBatch([...]) becomes eusend.batch.send([...]), up to 100 messages per request. If you use Postmark templates, eusend has server-side templates too — pass templateId instead of a body; see the docs for the variable syntax.

If you send over SMTP

Anything pointed at Postmark's SMTP endpoint moves by swapping credentials — no code change. One thing to get right: eusend uses implicit TLS on port 465 (the connection is encrypted from the first byte); STARTTLS on 587 is not supported.

nodemailer
// Before: smtp.postmarkapp.com:587, server token as user and pass
// After — note the port: eusend is implicit TLS on 465, not STARTTLS on 587
const transport = nodemailer.createTransport({
  host: 'smtp.eusend.dev',
  port: 465,
  secure: true,
  auth: { user: 'eusend', pass: process.env.EUSEND_API_KEY },
})

Webhooks

Postmark gives each event type its own webhook; eusend sends all events to one endpoint and you subscribe to the types you want. The events map directly:

Postmark webhookeusend event
Deliveryemail.delivered
Bounceemail.bounced
Spam complaintemail.complained
Openemail.opened
Clickemail.clicked

Where Postmark relies on HTTP basic auth or IP allowlists to protect the endpoint, every eusend webhook is signed with HMAC-SHA256 — verify the signature and you're done. The webhook docs show the exact scheme.

What's honestly different

Before you flip production

Want it done for you?
The migration page has a copy-paste prompt that lets an AI coding agent do the whole swap against our live API reference. Or email support@eusend.dev — we're happy to help you move over.