Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Built-in services

Built-in services

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, 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). 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 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.

Markdown for agents: /docs/mail-packages-partials-and-email-sent.md.Built from Jadpo main at a707efb.