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 a Norwegian 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 <[email protected]>',
  To: '[email protected]',
  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 <[email protected]>',
  to: '[email protected]',
  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 [email protected] — we're happy to help you move over.