"""Minimal transactional email over SMTP (magic-link sign-in). Config comes from the environment (GOODNEWS_SMTP_*), supplied to the API container via its env_file. STARTTLS submission; plain-text with a simple HTML alternative. No third-party email API — just the configured relay. """ from __future__ import annotations import os import smtplib import ssl from email.message import EmailMessage from html import escape def smtp_configured() -> bool: return bool(os.environ.get("GOODNEWS_SMTP_HOST")) def _cfg() -> dict: return { "host": os.environ.get("GOODNEWS_SMTP_HOST", ""), "port": int(os.environ.get("GOODNEWS_SMTP_PORT", "587")), "user": os.environ.get("GOODNEWS_SMTP_USER", ""), "password": os.environ.get("GOODNEWS_SMTP_PASSWORD", ""), "sender": os.environ.get("GOODNEWS_SMTP_FROM", "upbeatBytes "), # Where a reader's reply should land; falls back to the From address. "reply_to": os.environ.get("GOODNEWS_REPLY_TO_EMAIL", ""), } def send_email(to: str, subject: str, text: str, html: str | None = None, reply_to: str | None = None, headers: dict | None = None) -> None: """Send one message. Raises on failure (caller decides how loud to be).""" cfg = _cfg() if not cfg["host"]: raise RuntimeError("SMTP not configured (set GOODNEWS_SMTP_HOST)") msg = EmailMessage() msg["From"] = cfg["sender"] msg["To"] = to if reply_to: msg["Reply-To"] = reply_to for key, value in (headers or {}).items(): msg[key] = value msg["Subject"] = subject msg.set_content(text) if html: msg.add_alternative(html, subtype="html") context = ssl.create_default_context() with smtplib.SMTP(cfg["host"], cfg["port"], timeout=20) as server: server.ehlo() server.starttls(context=context) server.ehlo() if cfg["user"]: server.login(cfg["user"], cfg["password"]) server.send_message(msg) def send_feedback(to: str, category: str, message: str, contact: str | None, who: str) -> None: """Notify the admin of new user feedback (plain text is plenty here).""" subject = f"upbeatBytes feedback · {category}" reply = contact or "(none given)" text = ( f"New feedback ({category})\n" f"From: {who}\n" f"Reply to: {reply}\n\n" f"{message}\n" ) send_email(to, subject, text) def send_feedback_reply(to: str, reply_text: str, reply_html: str | None, original_message: str) -> None: """Reply to a reader's feedback from the admin inbox. Sends multipart text/plain + text/html (the HTML is the pre-sanitized Markdown render). Quotes their original note for context; exposes no analytics/account details.""" subject = "Re: Your upbeatBytes feedback" quoted = "\n".join("> " + line for line in (original_message or "").splitlines()) text = ( f"{reply_text}\n\n" "—\n" "In reply to your note to upbeatBytes:\n" f"{quoted}\n\n" "Thanks for reaching out.\n— upbeatBytes\n" ) body_html = None if reply_html: oq = escape(original_message or "").replace("\n", "
") body_html = ( '
' f"{reply_html}" '

In reply to your note to upbeatBytes:

' f'
{oq}
' "

Thanks for reaching out.
— upbeatBytes

" ) # Route the reader's reply to our chosen inbox (never back to the reader). cfg = _cfg() reply_to = cfg["reply_to"] or cfg["sender"] send_email(to, subject, text, html=body_html, reply_to=reply_to) def send_magic_link(to: str, link: str) -> None: """Send a calm, single-purpose sign-in email.""" subject = "Your upbeatBytes sign-in link" text = ( "Welcome back to upbeatBytes.\n\n" f"Tap to sign in:\n{link}\n\n" "This link works once and expires in 15 minutes.\n" "If you didn't request it, you can safely ignore this email." ) safe = escape(link, quote=True) html = ( '
' "

Welcome back to upbeatBytes.

" f'

' "Sign in

" '

This link works once and expires in 15 minutes. ' "If you didn't request it, you can safely ignore this email.

" "
" ) send_email(to, subject, text, html)