Email sending, templates, and notifications.
Supports multiple email providers via HTTP APIs: Resend, Postmark, SendGrid, and Mailgun.
Emails are rendered using Liquid templates with support for custom confirmation and admin notification templates.
Build the data object exposed to Liquid templates. Rows booked through a
package head the email by the package's name (listing_names); an order may
carry several bundles beside plain rows. hidePackageMembers (set for the
buyer's confirmation, not the admin notification) collapses each HIDDEN
package's member rows into one package row so members aren't revealed —
whatever else the order carries beside them.
Whether the buyer's confirmation email will be attempted: the email setup is active and the attendee carries an address. The owner's admin copy is a different message to a different reader. A send that delivers that copy alone has not attempted the buyer's confirmation. It must not clear the buyer's pending-email flag.
Cut a provider's reply to that length, marking the cut.
The single-row summary a hidden package collapses to for buyers: the bundle's summed price and quantity plus the widest member's dated stay (hiding members must not lose the date the buyer booked). Shared by the email body row and the SVG ticket, so the two can never disagree.
Host (everything after the last @) of a validated address. The ValidEmail
type guarantees a host is present, so there is no empty-host case to handle
and the compiler forbids passing a raw, unvalidated string.
Local part (everything before the last @) of a validated address.
Pull the reason out of a provider's error reply: the parsed message or the raw body. It comes on one line, capped, and with every email-shaped value blanked. Empty when the reply body says nothing.
Whether a string is a valid email (trimmed) per EmailSchema.
Parse and normalize a candidate email, returning the branded ValidEmail when it is valid or null otherwise. Use this in preference to isValidEmail when the validated address needs to be carried onward in a type-safe way.
For testing: reset the engines (so filters can be re-registered after currency changes)
signal cancels the request while it is in flight. null is the
everyday case: a send that nothing cancels.
Send an error notification to the configured ntfy URL Returns a promise so callers can await delivery if needed. Delivery failures are logged locally (via logErrorLocal) but never throw.
Updates the business email in the database. Pass empty string to clear the business email. Email is encrypted at rest.
Validate a Liquid template by parsing it (no rendering). Returns null if valid, or an error message string if invalid.
What one batch's reply said about the messages inside it.
Render all 3 parts (subject, html, text) using custom templates with fallback to defaults
One buyer-facing row group: a HIDDEN package's rows gather behind its name; every other row stands alone.
The email side's outcome, split by message. The buyer's confirmation is the message a pending flag speaks of. The owner's admin copy is a different message, and no pending flag represents it.
Data object passed to Liquid templates
An email address that has passed validation (a non-empty host containing at least one dot) and been normalized (trimmed, lowercased).
Group an order's entries for buyer-facing rendering (the confirmation body and its SVG tickets): each hidden package's rows collapse into one group sitting where its first row was, so a mixed order conceals every hidden bundle while its other rows render normally.
Format-only email schema: validates an address exactly as typed, without trimming or lowercasing. Used by field validators that check raw user input (see validateEmail in #templates/fields.ts).
Canonical email schema used across the app: local@host.tld. valibot's
email action guarantees a non-empty local part and a host containing at
least one dot. The input is trimmed and lowercased before validation, and the
output is branded as ValidEmail so a value can only be produced by passing
validation. All email validation that needs a normalized, carry-onward value
goes through this (see isValidEmail / parseEmail).
The host machine's own email credentials, read from its environment, with the hook a test uses to stand a different set in front of them.
The buyer's summed price (minor units) across an order's entries.
The bundle's summed booked quantity across an order's entries.
Usage
import * as mod from "docs/email.ts";