"""Operational mail for account links. No dialogue content, no model egress.""" from __future__ import annotations import logging import os import smtplib from email.message import EmailMessage from env_loader import env_flag, runtime_env log = logging.getLogger("kansho.mail") OUTBOX: list[dict] = [] class MailNotConfigured(RuntimeError): """SMTP is required and neither configured nor in the development log path.""" def clear_outbox() -> None: OUTBOX.clear() def smtp_configured() -> bool: return bool((os.environ.get("SMTP_HOST") or "").strip() and (os.environ.get("SMTP_FROM") or "").strip()) def send_mail(recipients: list[str], subject: str, body: str) -> None: cleaned: list[str] = [] for item in recipients: address = (item or "").strip().lower() if address and address not in cleaned: cleaned.append(address) if not cleaned: raise MailNotConfigured("Mailversand ist nicht konfiguriert") if env_flag("KANSHO_FAKE_MAIL"): OUTBOX.append({"to": cleaned, "subject": subject, "body": body}) return host = (os.environ.get("SMTP_HOST") or "").strip() sender = (os.environ.get("SMTP_FROM") or "").strip() if not host or not sender: if runtime_env() == "development": log.warning("Kansho mail (development, SMTP fehlt): %s", body) return raise MailNotConfigured("Mailversand ist nicht konfiguriert") port = int((os.environ.get("SMTP_PORT") or "587").strip() or "587") user = (os.environ.get("SMTP_USER") or "").strip() password = os.environ.get("SMTP_PASSWORD") or "" message = EmailMessage() message["Subject"] = subject message["From"] = sender message["To"] = ", ".join(cleaned) message.set_content(body) if port == 465: with smtplib.SMTP_SSL(host, port, timeout=20) as smtp: if user: smtp.login(user, password) smtp.send_message(message) return with smtplib.SMTP(host, port, timeout=20) as smtp: smtp.starttls() if user: smtp.login(user, password) smtp.send_message(message)