The panel could already show a user their configuration, put it behind a
share link or push it through the Telegram bot. Email was the one channel
missing, and the one most admins fall back to a hand-written script for.
Adds an SMTP block to /settings (host, port, none/STARTTLS/SSL, credentials,
sender, Reply-To, timeout) with a one-click test message, a "send by email"
action on every user card, and a mass send from the Users toolbar.
What travels: AmneziaWG / WireGuard peers as .conf attachments plus the
vpn:// key, Xray and Telemt as links -- their config *is* the link, so a
.conf would be a file no client can import. Files and links are independent
switches. The config text comes from the same manager call the share page
and the bot already use, so a protocol that renders in the UI renders in
the mail.
The mass send runs in a worker thread over a single SMTP session (a fresh
connection per message is what gets a sending account throttled) and the
dialog polls its progress: sent, skipped -- no address, disabled, no
connections yet -- and failed, each with the reason next to the name. One
unreachable node costs that one connection, not the message and not the run.
Details worth knowing:
- The stored SMTP password is never echoed back to the page; an empty field
on save keeps it, a blank one clears it.
- Messages are text plus an HTML alternative styled with the panel's own
light-theme tokens and appearance title/logo -- no third-party branding.
- A multi-line subject is folded instead of raising: EmailMessage refuses
linefeeds in headers.
- If the SMTP settings go bad between the click and the worker thread, the
job finishes with an error rather than staying "running" forever and
blocking every later run with a 409.
tests/test_mail_service.py covers the settings merge, the transport
(STARTTLS, login, reconnect after a dropped session), message shape and
attachment naming, the single send and every branch of the bulk counters,
plus the API surface (auth, password round-trip, translated errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>