"""Outgoing system mail (SPEC ยง6.1): mail the server sends on its own behalf. Today that is only the password-reset link. It goes out through the SMTP account the administrator configured in the admin panel (``app_settings``), never through a user's own mail account โ€” the whole point is that the user cannot sign in. The administrator's server is trusted, so unlike user mail accounts this connection is not held to the SSRF guard (it may well be a relay on the LAN). """ from __future__ import annotations import html as html_lib import logging from dataclasses import dataclass from email.message import EmailMessage from email.utils import formataddr, make_msgid import aiosmtplib from app.models import AppSettings from app.security.crypto import decrypt_secret logger = logging.getLogger(__name__) SENDER_NAME = "Ultimo Email" _TIMEOUT = 20.0 class MailerError(Exception): """System mail is not configured, or the SMTP server refused the message.""" @dataclass(frozen=True) class MailerConfig: host: str port: int security: str # starttls | ssl | none username: str password: str sender: str public_url: str def mailer_config(row: AppSettings) -> MailerConfig | None: """The usable configuration, or None while the admin has not set one up.""" if not (row.smtp_host and row.smtp_from and row.public_url): return None password = decrypt_secret(row.smtp_secret_ct).decode() if row.smtp_secret_ct else "" return MailerConfig( host=row.smtp_host, port=row.smtp_port, security=row.smtp_security, username=row.smtp_username, password=password, sender=row.smtp_from, public_url=row.public_url.rstrip("/"), ) async def send_mail(cfg: MailerConfig, to: str, subject: str, text: str, html: str) -> None: msg = EmailMessage() msg["From"] = formataddr((SENDER_NAME, cfg.sender)) msg["To"] = to msg["Subject"] = subject msg["Message-ID"] = make_msgid(domain=cfg.sender.rsplit("@", 1)[-1]) msg["Auto-Submitted"] = "auto-generated" # RFC 3834: do not auto-reply to this msg.set_content(text) msg.add_alternative(html, subtype="html") try: await aiosmtplib.send( msg, hostname=cfg.host, port=cfg.port, username=cfg.username or None, password=cfg.password or None, use_tls=cfg.security == "ssl", start_tls=cfg.security == "starttls", timeout=_TIMEOUT, ) except (aiosmtplib.SMTPException, OSError) as exc: raise MailerError(str(exc) or type(exc).__name__) from exc def reset_mail(link: str, handle: str) -> tuple[str, str, str]: """(subject, text, html) of the password-reset message.""" subject = "Reset your Ultimo Email password" text = ( f"Someone asked to change the password of the Ultimo Email account {handle}.\n\n" f"To choose a new password, open this link within 30 minutes:\n\n{link}\n\n" "The link works once. If you did not ask for this, ignore this email โ€” " "your password stays as it is.\n" ) h, u = html_lib.escape(handle), html_lib.escape(link, quote=True) muted = "font-size:13px;color:#6b7280" button = ( "display:inline-block;background:#2563eb;color:#ffffff;text-decoration:none;" "padding:10px 18px;border-radius:8px;font-weight:bold" ) body = "font-family:Arial,Helvetica,sans-serif;font-size:15px;color:#1f2937;line-height:1.5" html = ( f'\n\n' f"

Someone asked to change the password of the Ultimo Email account {h}.

\n" "

To choose a new password, open this link within 30 minutes:

\n" f'

Change password

\n' f'

Or paste this address into your browser:
{u}

\n' f'

The link works once. If you did not ask for this, ignore this ' "email โ€” your password stays as it is.

\n\n" ) return subject, text, html