Switch from Postmark
Keep the Postmark library and send through OpenEmail. Change its host and its server token, and your sending code stays as it is.
What to change
Point the library at https://api.openemail.uk/compat/postmark and put an OpenEmail API key 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 OpenEmail.
import { ServerClient } from 'postmark' const client = new ServerClient(process.env.OPENEMAIL_API_KEY, { requestHost: 'api.openemail.uk/compat/postmark',}) await client.sendEmail({ From: '[email protected]', To: '[email protected]', 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.
| Postmark | In OpenEmail |
|---|---|
| From | The sender, with its name. |
| To | Recipients separated by commas. With Cc and Bcc, up to 50 per message. |
| ReplyTo | One reply-to address. |
| Subject | The subject. |
| HtmlBody | The HTML body. TextBody becomes the text body, and one of the two is required. |
| Headers | Custom headers given as Name and Value: X-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID. |
| Attachments | Files, 20 at most and 5 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. |
| Tag | A tag named tag. |
| Metadata | Tags with the same names and values. With Tag, at most 10 per message. |
| TrackOpens | Turns open tracking on or off for the message. |
| TrackLinks | HtmlAndText and HtmlOnly turn click tracking on, and None turns it off. |
| MessageStream | outbound, or the id of any other transactional stream, sends the message as usual. |
| TemplateAlias | The slug or id (tpl_...) of an OpenEmail template, 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 OpenEmail and send its slug or id asTemplateAlias.- The
broadcaststream, with ErrorCode 1236. These endpoints send transactional mail, and newsletters go out as OpenEmail broadcasts. Subject,HtmlBodyorTextBodyon a templated message, with ErrorCode 1123, because the template supplies them.TrackLinksset toTextOnly, because OpenEmail 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
Metadataname 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,ErrorCode0 andMessageOK.MessageIDis the OpenEmail message id, the oneGET /emails/{id}and webhooks use. AnIdempotency-Keyheader 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
ErrorCodeandMessage, and the others still go out. - Errors come back as
ErrorCodeandMessage. A missing or unknown key, or one withoutemails: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.