Skip to the documentation
Knowledge base

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.

MailgunIn OpenEmail
fromThe sender, with its name.
toRecipients, repeated or separated by commas. With cc and bcc, up to 50 per message.
subjectThe subject.
htmlThe HTML body. text becomes the text body, and one of the two, or template, is required.
attachmentFiles, 20 at most and 5 MB in all.
inlineAn 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:tagTags 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:deliverytimeA scheduled send, up to a year ahead. A time already past sends at once.
o:trackingWith o:tracking-clicks and o:tracking-opens, turns tracking on or off for the message. htmlonly counts as on.
o:testmodeyes records the message as sent without delivering it, as an oe_test_ key does.
h:Reply-ToThe reply-to address. Any other h: field becomes a custom header: X-*, List-*, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID.
recipient-variablesA 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.
templateThe 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 template with html or text, because an OpenEmail template supplies the whole body, and a t:version that is not a version number.
  • o:deliverytime-optimize-period and o:time-zone-localize, because OpenEmail does not pick a send time for each recipient. Other h:X-Mailgun- headers, which are instructions to Mailgun: use the matching o: option instead.
  • amp-html on its own. Next to html or text it 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 an id: the OpenEmail message id in angle brackets, which GET /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. An Idempotency-Key header 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 with Domain not found. Everything else comes back as a message: 400 for a message that cannot be sent, 403 for a key without emails: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.