Email: one branded HTML layout with a text part, and invites can be emailed (#5917)

Each email was a subject and a plain-text f-string at its call site.
mail_templates.py now holds one layout: the site name, a heading, the
message, a button for the link and a footer saying who sent it and why.
Every email (password reset, test, invite) is a function returning its
subject, text and HTML. mailer.send sends them as multipart/alternative,
text first.

The HTML is email-shaped: tables, inline styles, no images or fonts, and
every value from settings or a person escaped.

POST /api/invites takes send_email. It is refused before anything is made
when email is off or the invite names no address. The link is built from
the public address. A failed send leaves the invite standing with
email_error, so the admin still gets the link once. The invite form offers
"Email the link to …" when email is on and an address is filled in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 13:43:13 -04:00
co-authored by Claude Opus 5.5
parent 5cc71c3050
commit c0de50d85d
9 changed files with 291 additions and 33 deletions
+2 -1
View File
@@ -28,7 +28,8 @@ on a LAN.
To let someone else in, send them an invite: **Settings → People → Invites** makes a link that
works once, expires (a week by default), and can be pinned to one email address. It
lets that one person register while registration stays closed.
lets that one person register while registration stays closed. With email set up (below) and an
address filled in, **Create and email** sends the link to that address as well.
**2. Terminate TLS in front of it, and forward the scheme.** The app marks the
session cookie `Secure` and sends HSTS only when it can tell the request arrived over