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.
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, 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.
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";