Migration guide

Move from Postmark to toSend.

Postmark's API maps closely onto ours. Most apps need a new client, lower-case field names and an array for recipients. Everything else can stay where it is while you move.

The plan

Run both providers side by side and move one app or site at a time. Nothing has to switch over in one go.

  1. 1

    Create a toSend account and add your sending domain. Postmark's DKIM and Return-Path records use different names from ours (tosend._domainkey and send), so both can live on the same domain. We verify the domain automatically once the records are visible.

  2. 2

    Create a sending key. If an app only sends from one domain, limit its key to that domain.

  3. 3

    In one app, swap the Postmark call for toSend (below), or change its SMTP settings. Keep everything else on Postmark.

  4. 4

    If you handle bounces, point a toSend webhook at the same handler and map the event names from the table below.

  5. 5

    Send a test, find it in your toSend logs, then move the next app. Keep Postmark until the last one has moved.

The same email, before and after

Postmark (npm install postmark)
import { ServerClient } from "postmark";

const client = new ServerClient(process.env.POSTMARK_TOKEN);

await client.sendEmail({
  From: "Your App <hello@yourdomain.com>",
  To: "user@example.com",
  Subject: "Your receipt",
  HtmlBody: "<p>Thanks for your order.</p>",
});
toSend (npm install tosend)
import { ToSend } from "tosend";

const tosend = new ToSend(process.env.TOSEND_API_KEY);

await tosend.send({
  from: { email: "hello@yourdomain.com", name: "Your App" },
  to: [{ email: "user@example.com" }],
  subject: "Your receipt",
  html: "<p>Thanks for your order.</p>",
});

Recipients change from a comma-separated string to an array of objects, and From becomes an object with email and name.

Concepts

PostmarktoSend
Server (one per app or client)Team (one per client), or one team with several domains
Message stream (transactional, broadcast)No streams. Keep marketing on a separate domain or team if you want it apart
Sender signature or domainVerified domain (unlimited)
Server API tokenSending key, optionally limited to one domain
Account API tokenAdmin key with the scopes you choose

Send API fields

Postmark uses PascalCase field names and strings for recipients. toSend uses snake_case and objects.

PostmarktoSend
POST https://api.postmarkapp.com/emailPOST https://api.tosend.com/v2/emails
X-Postmark-Server-Token headerAuthorization: Bearer tsend_...
200 with MessageID200 with {"message_id": "..."}
From: stringfrom: {"email", "name"}
To: comma-separated stringto: array of {"email", "name"}, up to 50
Cc / Bcccc / bcc: arrays like to
ReplyToreply_to: object or "Name <email>" string
Subjectsubject
HtmlBody / TextBodyhtml / text (text is generated if you send only HTML)
Headers: array of {"Name", "Value"}headers: object of string keys and values
Attachments: {"Name", "Content", "ContentType"}attachments: {"name", "type", "content"}, base64, 10 MB total
Tag, MetadataNo equivalent. Store the returned message_id against your own record
TemplateId + TemplateModelNo stored templates. Render the HTML in your app
MessageStreamNo equivalent
POST /email/batch, up to 500POST /v2/emails/batch, up to 100 emails

SMTP settings

PostmarktoSend
smtp.postmarkapp.comsmtp.tosend.com
25, 2525, 587465 or 2465 (TLS), 587 or 2587 (STARTTLS)
Server token (or SMTP access key)Any value, it's ignored
Server token (or SMTP secret key)Your toSend sending key

Webhook events

toSend sends two events, bounced and complaint, once each with no retry. Hard-bounced and complained addresses are suppressed automatically whether or not you listen.

PostmarktoSend
RecordType: Bounce (HardBounce, SoftBounce...)bounced, with is_hard_bounce true or false
RecordType: SpamComplaintcomplaint
Delivery, Open, Click, SubscriptionChangeNot sent

Before you switch off Postmark

  • Stored templates: render them to HTML in your app before sending.
  • Tags and metadata: store the returned message_id with your own record instead.
  • Open and click tracking, and delivery events: toSend doesn't have them, so remove any code that waits for them.
  • Suppression list: export it and filter those addresses out of your own lists. toSend can't import one, and starts suppressing new bounces and complaints from your first send.
  • Message streams: if you send broadcasts through Postmark, decide whether they move too. Our acceptable use policy covers marketing email.

Migration questions

How long does a Postmark migration take?

For one app, usually under an hour of work plus the wait for DNS. Swapping the client and field names is the main change.

Can I keep Postmark for some apps?

Yes. The DNS records don't overlap, so both services can send from the same domain for as long as you need.

What replaces Postmark's 45-day history?

toSend keeps 14 days of logs. If you need longer, store the message_id and the details you care about in your own database when you send.

Postmark details from postmarkapp.com/developer, checked 28 September 2026.

Other guides: from Mailgun · from SendGrid · from Resend · switch a FluentSMTP site · all guides

Ready to send? Your first 10,000 are on us.

Every feature included. No tiers. For one website, one app, or fifty client sites.

No credit card10,000 emails freePay as you go