Spaces:
Running
Running
| """SMTP delivery of an approved brief. | |
| Free tier by design: any SMTP server works, including Gmail with an app | |
| password. Delivery is *never* attempted for a brief the human did not approve — | |
| that check lives in the graph, and this module simply refuses to run without | |
| configuration rather than failing a run. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| import smtplib | |
| import ssl | |
| from dataclasses import dataclass | |
| from email.message import EmailMessage | |
| from app.core.settings import Settings, get_settings | |
| from app.models.brief import Brief | |
| from app.services.render import render_html, render_markdown | |
| logger = logging.getLogger(__name__) | |
| class DeliveryResult: | |
| """Outcome of an attempted send.""" | |
| sent: bool | |
| channel: str | |
| detail: str | |
| recipients: list[str] | |
| def to_dict(self) -> dict[str, object]: | |
| return { | |
| "sent": self.sent, | |
| "channel": self.channel, | |
| "detail": self.detail, | |
| "recipients": self.recipients, | |
| } | |
| def _build_message(brief: Brief, settings: Settings) -> EmailMessage: | |
| message = EmailMessage() | |
| subject = f"AlphaBrief — {brief.generated_for}" | |
| if brief.partial: | |
| subject += " (partial coverage)" | |
| message["Subject"] = subject | |
| message["From"] = settings.smtp_from or "" | |
| recipients = [r.strip() for r in (settings.smtp_to or "").split(",") if r.strip()] | |
| message["To"] = ", ".join(recipients) | |
| message.set_content(render_markdown(brief)) | |
| message.add_alternative(render_html(brief), subtype="html") | |
| return message | |
| def _send_blocking(brief: Brief, settings: Settings) -> DeliveryResult: | |
| recipients = [r.strip() for r in (settings.smtp_to or "").split(",") if r.strip()] | |
| message = _build_message(brief, settings) | |
| context = ssl.create_default_context() | |
| host = settings.smtp_host or "" | |
| with smtplib.SMTP(host, settings.smtp_port, timeout=settings.smtp_timeout_seconds) as server: | |
| server.ehlo() | |
| if server.has_extn("starttls"): | |
| server.starttls(context=context) | |
| server.ehlo() | |
| if settings.smtp_username and settings.smtp_password: | |
| server.login(settings.smtp_username, settings.smtp_password) | |
| server.send_message(message) | |
| return DeliveryResult( | |
| sent=True, | |
| channel="smtp", | |
| detail=f"delivered to {len(recipients)} recipient(s) via {host}", | |
| recipients=recipients, | |
| ) | |
| async def send_brief(brief: Brief, settings: Settings | None = None) -> DeliveryResult: | |
| """Send an approved brief by email, degrading to a no-op when unconfigured.""" | |
| cfg = settings or get_settings() | |
| if not cfg.smtp_enabled: | |
| return DeliveryResult( | |
| sent=False, | |
| channel="none", | |
| detail="SMTP is not configured; the brief was archived but not emailed", | |
| recipients=[], | |
| ) | |
| try: | |
| return await asyncio.to_thread(_send_blocking, brief, cfg) | |
| except Exception as exc: # noqa: BLE001 - a mail outage must not fail an approved run | |
| logger.warning("SMTP delivery failed: %s", exc) | |
| return DeliveryResult( | |
| sent=False, | |
| channel="smtp", | |
| detail=f"delivery failed: {type(exc).__name__}: {exc}", | |
| recipients=[r.strip() for r in (cfg.smtp_to or "").split(",") if r.strip()], | |
| ) | |