From 9917b07312d976548178f6ec623d363e07fc8c3b Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Wed, 23 Sep 2026 19:50:22 +0200 Subject: [PATCH] README: mail is content - `mail:` on a state, `mail:` in site.yaml, gdo Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 725697a..bdb0c9f 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,37 @@ it>` naming the automation that does. A bucket nothing reads fails lint, so no publicly collected answer can land where no one will ever see it. +**Mail is content** (portal 0.3.43, delivered by +[gdo](https://project.uhhm.no/uhhm/gdo)). A state in `aggregates.yaml` +may carry the mail its arrival sends: + +```yaml +states: + open: { event: received, mail: { to: submitter, subject: "Got it, {name}", body: "We read every one. Withdraw it here: {site}/decide/brief?chain={chain}&email={email}" } } + fixed: { event: fixed, mail: { to: board@example.no, subject: "Fixed: {where}", body: "..." } } +``` + +`to` is `submitter` (the record's `email` field; lint then requires +every form recording into that bucket to collect a required `email`) +or one fixed address. Subject and body are templates over the +record's fields, plus `{site}` (the instance's public URL), `{chain}` +(the record id), `{email}`, `{bucket}`, `{state}`; the body is +markdown, sent as plain text with an HTML alternative. An invite +(`invite:` on an alternative) mails its one-time link built in, in the +site's `lang`. None of it is sent unless `site.yaml` says who it comes +from: + +```yaml +mail: + from: UHHM + reply_to: bl@uhhm.no # optional + invite: { subject: "...", body: "..." } # optional rewording; {name} {username} {email} {group} {link} {site} {site_name} {expires} +``` + +Portal renders each mail the moment the record enters the state and +publishes it on `portal.mail.send` in the `WORMHOLE` JetStream stream; +gdo, one per host, hands it to the host's SMTP. A transition that +fires twice sends once. ## Routing: the tree is the router The `questions/` directory tree is the URL tree — `index.yaml` names