Mail is content: a state's mail: and the invite mail, published for gdo

`mail: { to, subject, body }` on a state in aggregates.yaml sends when a
record enters that state, `to: submitter` or one address, templates
over the record's fields plus {site}, {chain}, {email}. An invite mails
its one-time link built in, reworded by `mail.invite` in site.yaml,
which also carries the sender (`mail.from`, `mail.reply_to`); without
it a site sends nothing and says so in the log. Portal renders the
text and an HTML alternative and publishes one message per state
change on portal.mail.send in the WORMHOLE stream, id <bucket>:<record>
:<state> so a retried transition sends once; uhhm/gdo holds the
durable consumer and talks SMTP. The lint requires a required email
field on every form recording into a bucket that mails the submitter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-23 19:45:48 +02:00
co-authored by Claude Fable 5.1
parent 6d60f8b9a3
commit 19c8f541de
9 changed files with 431 additions and 0 deletions
+26
View File
@@ -543,6 +543,32 @@ pub struct SiteConfig {
pub favicon: Option<String>,
#[serde(default)]
pub hero: HeroConfig,
/// Who this site's mail comes from. Absent means the site sends
/// nothing: a state's `mail:` and the invite mail are logged and
/// skipped, never half-sent (see `mail.rs`).
#[serde(default)]
pub mail: Option<MailConfig>,
}
/// The sender identity, and the one built-in mail's wording.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct MailConfig {
/// `Name <address>` or a bare address, on a domain the host's
/// mail server may send for.
pub from: String,
#[serde(default)]
pub reply_to: Option<String>,
/// Rewords the invite mail; the built-in text is in the site's
/// `lang`. `{name}`, `{username}`, `{email}`, `{group}`, `{link}`,
/// `{site}`, `{site_name}`, `{expires}`.
#[serde(default)]
pub invite: Option<MailTemplate>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct MailTemplate {
pub subject: String,
pub body: String,
}
/// What the landing page's hero is. `plain` is just the copy; `module`