Switch from Mailgun
Keep the Mailgun SDK and send through OpenEmail. Change its base URL and its key, and your sending code stays as it is.
What to change
Point the SDK at https://api.openemail.uk/compat/mailgun and give it an OpenEmail API key with the emails:send permission in place of the Mailgun key. It travels as the password of the same HTTP Basic sign-in, and the user name is not checked. The domain in the path has to be one of the workspace’s domains, and the From address decides whether a message may go out, as it does everywhere in OpenEmail.
import formData from 'form-data'import Mailgun from 'mailgun.js' const mailgun = new Mailgun(formData)const mg = mailgun.client({ username: 'api', key: process.env.OPENEMAIL_API_KEY, url: 'https://api.openemail.uk/compat/mailgun',}) await mg.messages.create('acme.com', { from: 'Acme Billing <[email protected]>', to: ['[email protected]'], subject: 'Your invoice', html: '<p>Your invoice is attached.</p>',})In Ruby, the second argument is the host and the path without a scheme. In PHP, the SDK keeps only the host of the endpoint it is given, so the path goes in through AddPathPlugin from php-http, which the SDK already installs. The official Python package may log a warning that the host is not Mailgun’s, and sends anyway. It also retries a request that failed with 429 or a 5xx, so OpenEmail answers 400 rather than 5xx once part of a batch has gone out.
What maps to what
POST /v3/{domain}/messages is the endpoint served, as multipart/form-data, which attachments need, or application/x-www-form-urlencoded. A field name ending in [] is read without it.
| Mailgun | In OpenEmail |
|---|---|
| from | The sender, with its name. |
| to | Recipients, repeated or separated by commas. With cc and bcc, up to 50 per message. |
| subject | The subject. |
| html | The HTML body. text becomes the text body, and one of the two, or template, is required. |
| attachment | Files, 20 at most and 5 MB in all. |
| inline | An image the HTML uses as cid: and its file name is embedded where it appears. Any other inline file arrives as an ordinary attachment. |
| o:tag | Tags named tag, tag_2 and so on, each holding one tag. |
| v: | Each variable becomes a tag with its name and value. With o:tag, at most 10 per message. |
| o:deliverytime | A scheduled send, up to a year ahead. A time already past sends at once. |
| o:tracking | With o:tracking-clicks and o:tracking-opens, turns tracking on or off for the message. htmlonly counts as on. |
| o:testmode | yes records the message as sent without delivering it, as an oe_test_ key does. |
| h:Reply-To | The reply-to address. Any other h: field becomes a custom header: X-*, List-*, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID. |
| recipient-variables | A batch send. Every to address gets its own message, with %recipient.key% filled in from its variables and %recipient% as its address, and cc and bcc go on each one. A placeholder without a value is left as it is. |
| template | The slug or id (tpl_...) of an OpenEmail template, filled in from t:variables, or else from h:X-Mailgun-Variables. t:version picks a version by its number. |
What is refused, and why
- A
templatewithhtmlortext, because an OpenEmail template supplies the whole body, and at:versionthat is not a version number. o:deliverytime-optimize-periodando:time-zone-localize, because OpenEmail does not pick a send time for each recipient. Otherh:X-Mailgun-headers, which are instructions to Mailgun: use the matchingo:option instead.amp-htmlon its own. Next tohtmlortextit is left out, because those still carry the message.- More than one reply-to address, more than 10 tags, a tag name other than letters, digits,
_and-, and a batch of more than 100 recipients. Mailgun takes 1,000, so split larger batches.
o:dkim, o:require-tls, o:skip-verification, o:sending-ip, o:sending-ip-pool, o:tracking-pixel-location-top, o:archive-to, o:deliver-within and t:text are accepted and change nothing.
Responses and errors
- A send answers 200 with the message
Queued. Thank you.and anid: the OpenEmail message id in angle brackets, whichGET /emails/{id}and webhooks use without them. A batch send makes one message per recipient, each with its own id, and answers with the first. AnIdempotency-Keyheader works as it does on the rest of the API. - A missing or unknown key answers 401 with the plain text
Forbidden, and a domain the workspace does not have answers 404 withDomain not found. Everything else comes back as amessage: 400 for a message that cannot be sent, 403 for a key withoutemails:send, a From address the key may not use, a domain that cannot send yet or a workspace that has used up its sending allowance, and 413 for a body over 25 MB or attachments over 5 MB. - When one recipient of a batch fails after others were accepted, the error names the messages already sent and answers 400, so an SDK that retries does not send them twice.