""" utils/email.py — SMTP email helper. Reads server settings from app_settings (email.smtp_*) with .env fallbacks. """ import logging import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from config import get_setting logger = logging.getLogger("utils.email") def send_email(to, subject: str, body_text: str, body_html: str = None) -> None: """Send email via configured SMTP. `to` may be a str or list[str]. Raises on failure.""" host = get_setting("email.smtp_host", "").strip() port = get_setting("email.smtp_port", "587").strip() user = get_setting("email.smtp_user", "").strip() passwd = get_setting("email.smtp_password", "").strip() from_ = get_setting("email.smtp_from", "").strip() or user if not host: raise ValueError("SMTP host not configured. Add it via Admin → Settings.") try: port = int(port) except (ValueError, TypeError): port = 587 recipients = [to] if isinstance(to, str) else list(to) if not recipients: raise ValueError("No recipients specified.") if body_html: msg = MIMEMultipart("alternative") msg.attach(MIMEText(body_text, "plain")) msg.attach(MIMEText(body_html, "html")) else: msg = MIMEText(body_text, "plain") msg["Subject"] = subject msg["From"] = from_ msg["To"] = ", ".join(recipients) with smtplib.SMTP(host, port, timeout=15) as smtp: smtp.ehlo() try: smtp.starttls() smtp.ehlo() except smtplib.SMTPException: pass # server may not support STARTTLS if user and passwd: smtp.login(user, passwd) smtp.sendmail(from_, recipients, msg.as_string()) logger.info("Email sent to %s: %r", recipients, subject)