Mail packages, partials and email.sent
On this page
This topic has the exact rules for partials, for email.sent in tests,
and for a mail package that fills the email capability
(Email has the overview).
A partial's locale goes with it
Every EmailPartial has locale: before its own fields, as every email
has to: and locale:. The template that includes the partial gives
it, never the include itself: declaring locale: on a partial, or
giving it in an include, is refused.
A template includes a partial by its type's name in lower_snake_case,
{{ include button(...) }}, giving each field once, by name. A field
that may be none may be left out. Partials' names are unique in the
app, as emails' are.
Which partials a template reaches
A template reaches the partials in shared/emails/ and those in its own
domain's emails/. The shared layout reaches only the shared ones.
Another domain's partial is email.partial_invalid, its next: saying
to move the partial to shared/emails/.
What an include writes
An HTML template includes the partial's .html, and a text one its
.txt, which must exist. What the partial writes goes in as it is,
since its own values are escaped already. A partial may include others,
but never lead back to itself, directly or through others.
A partial's field that none of its templates reads is
email.field_unused. A partial that no template includes isn't
reported. An EmailMessage in shared/emails/ is layout.wrong_folder,
because an email is sent by a domain.
email.sent in a test
email.sent is a List<SentEmail> of the mail caught so far, in the
order it was sent. A SentEmail holds:
to,fromandsubject;htmlandtext, each aText?;links: eachhrefin the HTML and eachhttp://orhttps://address in the text, once each, in order;attachments: their file names;sign_in_token, aSignInToken?, andsign_in_code, aSignInCode?: the sign-in email's link token and code, which a route test posts to the confirmation (In development and tests). Both arenoneon every other email. Only a host that catches mail is handed them, beside the message, so no driver ever sees them.
A local variable named email is read before the namespace, since an
address is often called that. A test that reaches no store reads an
empty list.
One fake for every connection that fills email
A test's host catches the rendered message whole for the connection
email: names, a mail package's included. That package's own fake then
doesn't answer the send.
A mail package's mapping
A mail package fills email with a provides/email.jadpo, which gives
send: (with call:, input:, receipt: and failures:) and
limits: { attachments: }.
input:keys are the names of the operation's input fields. Their wire names come from the types, asprovide receiving's do (Capabilities shows Postmark's wire names).- Every part of the message is given somewhere:
from,to,subject,html,text, and the attachments as{ each: message.attachments as: { } }, with each one'sitem.nameanditem.content. Its media type,item.content_type, is optional.htmlandtextgo only to fields that may benone, the content only toBytes, sent in base64, and the rest to text. receipt:is optional and names text in the answer. It is read, and not kept anywhere yet.failures:maps the operation's failure codes ontoEmailFailure.recipient_rejectedor.message_rejected. A failure it doesn't map ends the delivery as the package's own.limits:is required.
Attachments within the limit
Through a mail package, each File field of an email gives
max_size:, and their sum is at most the package's limit
(email.attachment_limit). A send whose attachments are larger still is
refused as message_rejected before it goes out. Through SMTP, which
declares no limit, max_size: is optional.
The connection
email: may name a package connection whose package has
provides/email.jadpo. A connection that only email: names needs no
domains:, since no domain's action calls it. The delivery calls the
operation as any provider call is made, with no key, so a server's error
after the message went out parks it as outcome_unknown.
An email without an .html or a .txt, or an attachment without a
media type, sends the provider's input without that field, rather than
with null, which a provider needn't accept.
Resend
Resend is the second mail connection, in packages/resend. As Resend's
documentation gives it (resend.com/docs, "Send Email" and "Errors"):
- It sends with
POST /emailsandAuthorization: Bearer, givingfrom,to,subject,htmlandtext(both optional, the text made from the HTML when left out), andattachmentsof{ filename, content (base64), content_type? }. - An email may be 40 MB once its attachments are in base64, so its limit is 30 MB of files.
- Its refusals of the message (
validation_error,missing_required_field,missing_required_parameter,invalid_attachment,invalid_parameter) aremessage_rejected. - Its rate and quota limits (
rate_limit_exceeded,daily_quota_exceeded,monthly_quota_exceeded, all429) mean nothing was sent, so they areunavailableand retried, never a failure of the message. - A refused key (
401,403) ismisconfigured.
Its connection gives host:, https://api.resend.com.
Markdown for agents: /docs/mail-packages-partials-and-email-sent.md.Built from Jadpo main at a707efb.