"""Outbound email for onboarding (access requests + approval notices). Gmail SMTP + App Password by default. Deliberately fail-soft: if SMTP isn't configured or a send errors, we log and return False so the caller can fall back to in-app notifications — email is never load-bearing for the app's core flow. """ import logging import smtplib import ssl from email.message import EmailMessage from app.config import settings log = logging.getLogger("subfeed.email") def email_enabled() -> bool: return bool(settings.smtp_host and settings.smtp_user and settings.smtp_password) def _send(to: list[str], subject: str, body: str) -> bool: recipients = [e for e in to if e] if not recipients: return False if not email_enabled(): log.info("SMTP not configured; skipping email %r to %s", subject, recipients) return False msg = EmailMessage() msg["Subject"] = subject msg["From"] = settings.smtp_from or settings.smtp_user msg["To"] = ", ".join(recipients) msg.set_content(body) try: with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=20) as s: s.starttls(context=ssl.create_default_context()) s.login(settings.smtp_user, settings.smtp_password) s.send_message(msg) log.info("Sent email %r to %s", subject, recipients) return True except Exception: log.exception("SMTP send failed (%r to %s)", subject, recipients) return False def send_access_approved(email: str) -> bool: return _send( [email], "Your Subfeed access is approved", "Good news — your access to Subfeed has been approved.\n\n" "Sign in with this Google account to start browsing your subscriptions.\n", ) def send_admin_new_request(admins: list[str], requester: str) -> bool: return _send( admins, "Subfeed: new access request", f"{requester} has requested access to Subfeed.\n\n" "Approve or deny it in Settings → Admin.\n", )