Skip to the documentation
Knowledge base

Switch from SendGrid

Keep the SendGrid 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/sendgrid and give it an OpenEmail API key with the emails:send permission in place of the SendGrid key. It travels in the same Authorization: Bearer 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 sgMail from '@sendgrid/mail'import client from '@sendgrid/client' client.setApiKey(process.env.OPENEMAIL_API_KEY)client.setDefaultRequest('baseUrl', 'https://api.openemail.uk/compat/sendgrid')sgMail.setClient(client) await sgMail.send({  from: '[email protected]',  to: '[email protected]',  subject: 'Your invoice',  html: '<p>Your invoice is attached.</p>',})

In Node, set the key on the client first, then the base URL, then hand the client to the mail package. Do not call sgMail.setApiKey after that, because it puts the base URL back to SendGrid. The SDK warns that the key does not start with SG., which is harmless. In Python, Ruby and PHP, give the host without a trailing slash.

What maps to what

POST /v3/mail/send is the endpoint served. Each entry in personalizations becomes its own OpenEmail message with its own id, so one request sends at most 100 messages.

SendGridIn OpenEmail
fromThe sender, with its name. A personalization can name its own from.
personalizationsOne message each. Its to, cc and bcc hold up to 50 recipients between them, and its subject, headers, custom_args, send_at and substitutions apply to that message alone.
subjectThe subject, unless a personalization sets its own.
contenttext/plain becomes the text body and text/html the HTML body. text/x-amp-html is left out, because the HTML body still carries the message.
attachmentsFiles, 20 at most and 5 MB in all. An inline image whose content_id the HTML uses as cid: is embedded where it appears. Any other inline file arrives as an ordinary attachment.
reply_toThe reply-to address. reply_to_list works too while it holds one address.
headersCustom headers: X-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID. A personalization adds its own.
categoriesTags named category, category_2 and so on, each holding one category.
custom_argsTags with the same names and values. A personalization’s values win.
send_atA scheduled send, up to a year ahead. A time already past sends at once.
substitutionsEach key is replaced by its value in the subject, the text body and the HTML body of that message.
template_idThe id (tpl_...) or slug of an OpenEmail template, filled in from dynamic_template_data.
tracking_settingsopen_tracking.enable and click_tracking.enable turn open and click tracking on or off for the message.
mail_settingssandbox_mode.enable checks the request, the sender and the template, then answers 200 without sending anything.

A message carries at most 10 tags, counting categories and custom_args together. A request that needs more is refused rather than trimmed, so nothing you sent goes missing without a word.

What is refused, and why

  • A SendGrid template id in template_id, such as d-…. Templates stay at SendGrid, so recreate the template in OpenEmail and send its id or slug.
  • content next to template_id, because an OpenEmail template supplies the whole body. substitutions with a template for the same reason: pass the values in dynamic_template_data.
  • More than one reply-to address, reply_to and reply_to_list together, and content types other than text and HTML. Send a calendar invitation as an .ics attachment.
  • mail_settings.footer switched on, and sections, because OpenEmail does not write text into your message.
  • More than 10 tags, a tag name other than letters, digits, _ and -, a header outside the list above, and more than 100 personalizations in one request.

asm, batch_id, ip_pool_name, the bypass settings in mail_settings, subscription_tracking, ganalytics, click_tracking.enable_text and open_tracking.substitution_tag are accepted and change nothing. Addresses on the workspace’s suppression list are always skipped, whatever a bypass setting says.

Responses and errors

  • A send answers 202 with an empty body and the OpenEmail message id in X-Message-Id, the id that GET /emails/{id} and webhooks use. With several personalizations it holds the first message’s id. An Idempotency-Key header works as it does on the rest of the API.
  • Errors come back as errors, a list of message, field and help: 400 for a request that cannot be sent, 401 for a missing or unknown key, 403 for a key without emails:send, or a From address the key may not use or whose domain cannot send yet, 413 for a body over 30 MB or attachments over 5 MB, and 429 when the workspace has used up its sending allowance.
  • When a personalization fails after earlier ones were accepted, the error names the messages already sent, so a retry can leave them out.