Mailer
The definitions, options, providers and errors of @dotpress/mailer. For a guide, see Emails.
defineTemplate()
Templates are checked when they are defined: unknown options, a missing source, texts without the same locales, or a layout without translations in a locale of the template throw a TypeError. template.locales lists the locales.
defineLayout()
In MJML, place content inside <mj-body>: templates provide <mj-section> elements. A layout without translations can be used in any locale. Reusable blocks are plain functions returning MJML. mj-include is ignored: templates do not read files.
Rendering
- The template function runs with
t(), then the layout, which receives it ascontent. - MJML compiles the result to HTML, cached by template and locale.
- Mustache inserts the data in the HTML, the subject and the text version.
/, = and ` are not escaped, unlike the default of Mustache: many email clients do not decode them in links. Mustache sections work ({{#items}}…{{/items}}); partials do not. The subject is a single line: line breaks become spaces.
The text version, without a text source, is converted from the HTML by html-to-text: lines wrapped at 78 characters, links followed by their URL, images and hidden elements (such as the MJML preview text) left out.
Locales: the rendered locale is the first available among the requested one (fr-CA, compared case-insensitively), its base language (fr), the defaultLocale, then its base language. Otherwise, rendering fails. A template without translations takes the locales of its layout; without translations in both, the locale is ignored.
Data: validated by the schema with safeParseAsync (async refinements work), and the parsed data is rendered.
mailer.render(template, { locale, data }) returns { template, locale, subject, html, text }. renderMail(template, { locale, defaultLocale, data }) does the same without a mailer.
createMailer()
send()
An address is 'ada@example.com', 'Ada Lovelace <ada@example.com>' or { name, address }, alone or in a list. Recipients, attachments, headers, tags and metadata are checked before rendering: an address without @ or a line break in a name or a header throws a MailMessageError. messageId is the identifier given by the provider, if any.
mailer.sendMessage(options) sends a message rendered elsewhere: the same options, with subject, and html, text or both. Without text, it is converted from html.
Tags and metadata
Tags group messages in the statistics of the provider; metadata comes back in its webhooks, to link a delivery, a bounce or a click to your app.
- Tags and metadata keys: 1 to 256 letters, digits,
_or-, the strictest rule among providers. Duplicate tags are removed. - Metadata values: strings on one line.
A provider without tags or metadata ignores them: switching providers never breaks a call.
Providers
Every provider sends the sender, the reply-to addresses, several to, cc and bcc, the HTML and text versions, attachments (inline ones included) and additional headers, within the limits of its API. The providers based on an HTTP API (SendGrid, Postmark, Brevo, Mailjet, Mailgun, Scaleway) have no dependency, and accept baseUrl (region, proxy, tests) and fetch (replaces the global fetch); their errors are MailSendError, with the HTTP status and the message of the API.
- Memory:
outbox.sentlists the messages,last()returns the last one,clear()empties it. - SMTP: nodemailer 10, Node.js only. Install
nodemailer. SMTP errors (cause.responseCode, such as550) areMailSendError. - Amazon SES: the
SendEmailoperation of the API v2, with@aws-sdk/client-sesv23.782 or later. Credentials come from the environment. On AWS Lambda, the SDK of the Node.js runtime works, without bundling it, if its version is 3.782 or later. - Resend: install
resend. - SendGrid: the message ID is read from the
X-Message-Idresponse header, when present. - Postmark: the message ID is the
MessageIDof the response; errors include the PostmarkErrorCode. - Brevo: the message ID is the
messageIdof the response; errors include the Brevocode. - Mailjet: Mailjet returns an identifier per recipient: the message ID is the
MessageUUIDof the firsttorecipient (the numericMessageIDwithout UUID). A message rejected in a successful response is reported as an error too. - Mailgun: reply-to addresses and headers are sent as
h:options. An inline attachment is sent with itscontentIdas file name, socid:references work as with the other providers. The message ID is theidof the response. - Scaleway: the API has no reply-to field: it is sent as a
Reply-Toheader. The message ID is theidof the email of the first recipient.
Limits of the providers
Checked before calling the API: a message over a limit throws a MailMessageError.
Request sizes include the attachments, encoded in base64: a third larger than the files.
Custom providers
A provider has a name and a send(message) method, which returns { messageId } or nothing. The message is normalized and frozen: from, to, cc, bcc, replyTo (each { name?, address }), subject, html (undefined for a text-only message), text, attachments, headers, tags, metadata, and template and locale for the logs. formatAddress() formats an address for a header ("Ada" <ada@example.com>).
Throw a MailSendError with the HTTP status of the API, and a MailMessageError for a message the provider cannot send. Other errors are wrapped in a MailSendError.
mailerPlugin()
The plugin, named mailer, provides ctx.mailer: the mailer, logging with the request logger. Using ctx.mailer without the plugin throws an explicit error. It has its own entry point: the main one does not depend on @dotpress/core.
Logs
With a logger (a Dotpress or Pino logger, or any object with an info(data, message) method), each sent message is logged at the info level as Mail sent, with the provider, the template, the message ID and the number of recipients. Addresses are not logged. Failures are thrown, not logged.
Performance
MJML is loaded on the first render of an MJML template (about 100 to 300 ms), and html-to-text on the first text conversion. MJML compilation is cached by template and locale: later renders only run Mustache, in well under a millisecond.
Errors
The error classes extend MailerError.