alphabrief / apps /api /app /services /email.py
Abdr007's picture
AlphaBrief — deployed tree
69e310f
Raw
History Blame Contribute Delete
3.41 kB
"""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__)
@dataclass(frozen=True, slots=True)
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()],
)