""" SMTP Sender — sends real emails via SMTP (e.g., to Mailpit for dev). Uses standard smtplib. Configuration via environment variables: SMTP_HOST (default: localhost) SMTP_PORT (default: 1025) SMTP_FROM_NAME (default: BreakPilot Compliance) SMTP_FROM_ADDR (default: compliance@breakpilot.local) """ from __future__ import annotations import logging import os import smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText logger = logging.getLogger(__name__) SMTP_HOST = os.environ.get("SMTP_HOST", "localhost") SMTP_PORT = int(os.environ.get("SMTP_PORT", "1025")) SMTP_FROM_NAME = os.environ.get("SMTP_FROM_NAME", "BreakPilot Compliance") SMTP_FROM_ADDR = os.environ.get("SMTP_FROM_ADDR", "compliance@breakpilot.local") def send_email( recipient: str, subject: str, body_html: str, from_addr: str | None = None, from_name: str | None = None, attachments: list[dict] | None = None, ) -> dict: """Send an email via SMTP. Returns dict with status and message_id. attachments: optional list of dicts: [{"filename": "evidence.zip", "data": , "mime": "application/zip"}, ...] """ sender_addr = from_addr or SMTP_FROM_ADDR sender_name = from_name or SMTP_FROM_NAME if attachments: msg = MIMEMultipart("mixed") body = MIMEMultipart("alternative") body.attach(MIMEText(body_html, "html", "utf-8")) msg.attach(body) for att in attachments: mime = att.get("mime", "application/octet-stream") maintype, _, subtype = mime.partition("/") part = MIMEBase(maintype or "application", subtype or "octet-stream") part.set_payload(att.get("data", b"")) encoders.encode_base64(part) fname = att.get("filename", "attachment.bin") part.add_header( "Content-Disposition", f'attachment; filename="{fname}"', ) msg.attach(part) else: msg = MIMEMultipart("alternative") msg.attach(MIMEText(body_html, "html", "utf-8")) msg["From"] = f"{sender_name} <{sender_addr}>" msg["To"] = recipient msg["Subject"] = subject try: with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as server: server.sendmail(sender_addr, [recipient], msg.as_string()) att_count = len(attachments or []) logger.info( "Email sent to %s: %s (attachments=%d)", recipient, subject, att_count, ) return { "status": "sent", "recipient": recipient, "subject": subject, "attachments": att_count, } except Exception as e: logger.error("Failed to send email to %s: %s", recipient, e) return {"status": "failed", "recipient": recipient, "error": str(e)}