# Mail packages, partials and `email.sent`

This topic has the exact rules for partials, for `email.sent` in tests,
and for a mail package that fills the `email` capability
([Email](email.md) 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`, `from` and `subject`;
- `html` and `text`, each a `Text?`;
- `links`: each `href` in the HTML and each `http://` or `https://`
  address in the text, once each, in order;
- `attachments`: their file names;
- `sign_in_token`, a `SignInToken?`, and `sign_in_code`, a
  `SignInCode?`: the sign-in email's link token and code, which a route
  test posts to the confirmation
  ([In development and tests](in-development-and-tests.md)).
  Both are `none` on 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, as `provide receiving`'s do
  ([Capabilities](capabilities.md) 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's `item.name`
  and `item.content`. Its media type, `item.content_type`, is optional.
  `html` and `text` go only to fields that may be `none`, the content
  only to `Bytes`, 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 onto
  `EmailFailure.recipient_rejected` or `.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 /emails` and `Authorization: Bearer`, giving
  `from`, `to`, `subject`, `html` and `text` (both optional, the text
  made from the HTML when left out), and `attachments` of
  `{ 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`) are `message_rejected`.
- Its rate and quota limits (`rate_limit_exceeded`,
  `daily_quota_exceeded`, `monthly_quota_exceeded`, all `429`) mean
  nothing was sent, so they are `unavailable` and retried, never a
  failure of the message.
- A refused key (`401`, `403`) is `misconfigured`.

Its connection gives `host:`, `https://api.resend.com`.
