Skip to the documentation
Knowledge base

Switch from Postmark

Keep the Postmark library and send through Nextmail. Change its host and its server token, and your sending code stays as it is.

What to change

Point the library at https://api.nextmail.sh/compat/postmark and put an API key from Nextmail with the emails:send permission where the server token goes. It travels in the same X-Postmark-Server-Token header. Your calls that send mail stay as they are, and the From address decides whether a message may go out, as it does everywhere in Nextmail.

import { ServerClient } from 'postmark' const client = new ServerClient(process.env.NEXTMAIL_API_KEY, {  requestHost: 'api.nextmail.sh/compat/postmark',}) await client.sendEmail({  From: 'billing@acme.com',  To: 'ada@example.com',  Subject: 'Your invoice',  HtmlBody: '<p>Your invoice is attached.</p>',  MessageStream: 'outbound',})

In Node, requestHost is the host and the path together, with no scheme and no trailing slash. In Ruby, path_prefix needs a slash at both ends. In Python, use the official postmark-python package with base_url. The community postmarker package cannot reach a path below a host, so it does not work here. In PHP, PostmarkClient::$BASE_URL takes the scheme, the host and the path with no trailing slash. It is static, so it applies to every Postmark client in the process, PostmarkAdminClient included.

What maps to what

POST /email, /email/batch, /email/withTemplate and /email/batchWithTemplates are the endpoints served. Field names match in any case, as they do at Postmark, and an empty string counts as left out.

PostmarkIn Nextmail
FromThe sender, with its name.
ToRecipients separated by commas. With Cc and Bcc, up to 50 per message.
ReplyToOne reply-to address.
SubjectThe subject.
HtmlBodyThe HTML body. TextBody becomes the text body, and one of the two is required.
HeadersCustom headers given as Name and Value: X-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID.
AttachmentsFiles, 20 at most and 10 MB in all. An image whose ContentID the HTML uses as cid: is embedded where it appears. Any other file arrives as an ordinary attachment.
TagA tag named tag.
MetadataTags with the same names and values. With Tag, at most 10 per message.
TrackOpensTurns open tracking on or off for the message.
MessageStreamoutbound, or the id of any other transactional stream, sends the message as usual. broadcast sends it on the broadcast lane.
TemplateAliasThe slug or id (tpl_...) of a template in Nextmail, filled in from TemplateModel. InlineCss is accepted and changes nothing.

What is refused, and why

  • TemplateId, with ErrorCode 1101. A Postmark template id means nothing here, so recreate the template in Nextmail and send its slug or id as TemplateAlias.
  • The broadcast stream while the broadcast lane of the workspace is paused for too many bounces or complaints, with ErrorCode 1236. Otherwise a message on it travels on the broadcast lane, apart from app mail.
  • Subject, HtmlBody or TextBody on a templated message, with ErrorCode 1123, because the template supplies them. TrackLinks set to TextOnly, because Nextmail tracks the links in the HTML part.
  • More than one reply-to address, a header given twice or outside the list above, more than 10 tags, and a tag or Metadata name other than letters, digits, _ and -.
  • A batch of more than 100 messages, with ErrorCode 410. Postmark takes 500, so split larger batches.

Responses and errors

  • A send answers 200 with To, SubmittedAt, MessageID, ErrorCode 0 and Message OK. MessageID is the Nextmail message id, the one GET /emails/{id} and webhooks use. An Idempotency-Key header works as it does on the rest of the API.
  • A batch answers 200 with one result per message, in order. A message that failed carries only its ErrorCode and Message, and the others still go out.
  • Errors come back as ErrorCode and Message. A missing or unknown key, or one without emails:send, answers HTTP 401 with ErrorCode 10. The rest answer HTTP 422: ErrorCode 300 for the message itself, 400 for a From address the key may not use, 401 for a domain that cannot send yet, 402 for a body that is not JSON and 405 for a workspace that has used up its sending allowance. HTTP 413 means the body is over 10 MB, or 50 MB for a batch, or the attachments are over 5 MB.

Bring your account across

Migrations → From a provider reads a Postmark account with a Server token and brings across what can leave it. Running it twice duplicates nothing, and the token is held encrypted only while the import runs.

  • Suppressions arrive from every outbound stream with their reason: hard bounces as bounces, spam complaints as complaints and manual suppressions as manual.
  • Standard templates arrive with their layout merged in. A template whose layout is missing arrives with a warning.
  • Each webhook is registered with the same URL and the nearest matching events, switched off, because deliveries here are signed differently.
  • Server tokens come back by name so you can create their replacements. Domains and sender signatures need an Account token, which the import does not take, so add those domains here yourself. Postmark keeps no contact lists, so there are none to bring.