"""SMTP-backed transactional email. Sends multipart/alternative: a plain-text body for accessibility / minimal clients and an HTML body for richer rendering. Designed for cross-client robustness: - Inline styles on every element (Outlook desktop ignores
Your {brand} sign-in code — {code} — expires in {ttl_minutes} minutes.
▰ {brand_upper}
 
Your sign-in code
 
{code}
 
This code expires in {ttl_minutes} minutes. If you didn’t request it, you can safely ignore this email — no changes will be made to any account.
 
 
Sent automatically by {brand} · do not reply
""" def _html_template_filled(code: str, ttl_minutes: int) -> str: """Substitute palette + content into the OTP HTML template.""" return _OTP_HTML_TEMPLATE.format( code=code, ttl_minutes=ttl_minutes, brand=branding.BRAND_NAME, brand_upper=branding.BRAND_NAME.upper(), FONT_MONO=branding.FONT_MONO, **{f"L_{k.replace('-', '_')}": v for k, v in branding.LIGHT.items()}, **{f"D_{k.replace('-', '_')}": v for k, v in branding.DARK.items()}, ) _OTP_TEXT_TEMPLATE = """\ {brand_upper} — sign in Your verification code: {code} This code expires in {ttl_minutes} minutes. If you didn't request it, you can safely ignore this email — no changes will be made to any account. — Sent automatically by {brand} · do not reply """ def render_otp_email(code: str, ttl_minutes: int) -> tuple[str, str, str]: """Returns (subject, text_body, html_body). Subject embeds the code so users can read it directly from the inbox list without opening the message — common practice for OTP emails (Notion, Substack). The lock-screen exposure tradeoff is minimal: anyone with phone access who could see the notification could also open the email.""" subject = f"{branding.BRAND_NAME} sign-in: {code}" text = _OTP_TEXT_TEMPLATE.format( code=code, ttl_minutes=ttl_minutes, brand=branding.BRAND_NAME, brand_upper=branding.BRAND_NAME.upper(), ) html = _html_template_filled(code=code, ttl_minutes=ttl_minutes) return subject, text, html async def send_otp(to: str, code: str, ttl_minutes: int) -> None: subject, text, html = render_otp_email(code, ttl_minutes) await send_email(to, subject, text, html_body=html) # --------------------------------------------------------------------------- # Digest email rendering # --------------------------------------------------------------------------- _DIGEST_HTML_TEMPLATE = """\ {brand} — {label}
▰ {brand_upper} · {label_upper}
 
{content_html}
 
 
""" def _strip_html_to_text(html_body: str) -> str: """Best-effort HTML → plain text for the multipart fallback. We don't need perfection — just readable prose for clients that won't render HTML.""" text = _re.sub(r"(?i)<(/(p|h[1-6]|li|ul|ol)|br\s*/?)>", "\n", html_body) text = _re.sub(r"<[^>]+>", "", text) text = _html_lib.unescape(text) text = _re.sub(r"\n{3,}", "\n\n", text) return text.strip() def render_digest_email( *, kind: str, date_str: str, content_html: str, unsubscribe_url: str, settings_url: str, ) -> tuple[str, str, str]: """Returns (subject, text_body, html_body) for a digest email. `kind` is "daily" or "weekly". Anything else raises ValueError.""" if kind == "daily": label = "Daily" subject = f"{branding.BRAND_NAME} · Daily — {date_str}" elif kind == "weekly": label = "Weekly recap" subject = f"{branding.BRAND_NAME} · Weekly recap — {date_str}" else: raise ValueError(f"unknown digest kind: {kind!r}") html_body = _DIGEST_HTML_TEMPLATE.format( brand=branding.BRAND_NAME, brand_upper=branding.BRAND_NAME.upper(), label=label, label_upper=label.upper(), FONT_MONO=branding.FONT_MONO, content_html=content_html, unsubscribe_url=unsubscribe_url, settings_url=settings_url, **{f"L_{k.replace('-', '_')}": v for k, v in branding.LIGHT.items()}, **{f"D_{k.replace('-', '_')}": v for k, v in branding.DARK.items()}, ) text_lines = [ f"{branding.BRAND_NAME} — {label}", date_str, "", _strip_html_to_text(content_html), "", f"Unsubscribe: {unsubscribe_url}", f"Manage preferences: {settings_url}", ] text_body = "\n".join(text_lines) return subject, text_body, html_body