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.
| SendGrid | In OpenEmail |
|---|---|
| from | The sender, with its name. A personalization can name its own from. |
| personalizations | One 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. |
| subject | The subject, unless a personalization sets its own. |
| content | text/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. |
| attachments | Files, 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_to | The reply-to address. reply_to_list works too while it holds one address. |
| headers | Custom headers: X-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID. A personalization adds its own. |
| categories | Tags named category, category_2 and so on, each holding one category. |
| custom_args | Tags with the same names and values. A personalization’s values win. |
| send_at | A scheduled send, up to a year ahead. A time already past sends at once. |
| substitutions | Each key is replaced by its value in the subject, the text body and the HTML body of that message. |
| template_id | The id (tpl_...) or slug of an OpenEmail template, filled in from dynamic_template_data. |
| tracking_settings | open_tracking.enable and click_tracking.enable turn open and click tracking on or off for the message. |
| mail_settings | sandbox_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 asd-…. Templates stay at SendGrid, so recreate the template in OpenEmail and send its id or slug. contentnext totemplate_id, because an OpenEmail template supplies the whole body.substitutionswith a template for the same reason: pass the values indynamic_template_data.- More than one reply-to address,
reply_toandreply_to_listtogether, and content types other than text and HTML. Send a calendar invitation as an.icsattachment. mail_settings.footerswitched on, andsections, 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 thatGET /emails/{id}and webhooks use. With several personalizations it holds the first message’s id. AnIdempotency-Keyheader works as it does on the rest of the API. - Errors come back as
errors, a list ofmessage,fieldandhelp: 400 for a request that cannot be sent, 401 for a missing or unknown key, 403 for a key withoutemails: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.