""" E-postutsending for TeeCup. I dag kun magic-link-innlogging. Egne, uavhengige SMTP-credentials fra teeoff (ADR-009) -- se .env (TEECUP_SMTP_SERVER/PORT/USER/PASS, TEECUP_FROM_EMAIL). `smtplib` er synkron, så selve sendingen kjøres i en tråd (asyncio.to_thread) for ikke å blokkere event loop-en -- samme mønster som teeoffs egen, fungerende magic-link-utsending. Håndterer BEGGE vanlige SMTP-tilkoblingsmåter siden porten ikke er antatt på forhånd: implisitt TLS (SMTP_SSL, typisk port 465) eller STARTTLS på en vanlig tilkobling (typisk port 587/25). """ import smtplib from asyncio import to_thread from email.message import EmailMessage from .config import settings def _send_sync(to_email: str, subject: str, body: str) -> None: msg = EmailMessage() msg["Subject"] = subject msg["From"] = settings.FROM_EMAIL msg["To"] = to_email msg.set_content(body) if settings.SMTP_PORT == 465: with smtplib.SMTP_SSL(settings.SMTP_SERVER, settings.SMTP_PORT) as smtp: smtp.login(settings.SMTP_USER, settings.SMTP_PASS) smtp.send_message(msg) else: with smtplib.SMTP(settings.SMTP_SERVER, settings.SMTP_PORT) as smtp: smtp.starttls() smtp.login(settings.SMTP_USER, settings.SMTP_PASS) smtp.send_message(msg) _TEMPLATES = { "nb": { "subject": "Logg inn på TeeCup", "body": ( "Hei,\n\n" "Bruk denne koden for å logge inn på TeeCup innen {minutes} minutter:\n\n" "{token}\n\n" "Ba du ikke om dette, kan du se bort fra e-posten.\n" ), }, "en": { "subject": "Log in to TeeCup", "body": ( "Hi,\n\n" "Use this code to log in to TeeCup within {minutes} minutes:\n\n" "{token}\n\n" "If you didn't request this, you can ignore this email.\n" ), }, } async def send_magic_link_email(to_email: str, raw_token: str, locale: str = "nb") -> None: """Sender selve magic-link-tokenet som klartekst, på angitt språk. MERK: ingen klikkbar lenke ennå -- TeeCup har ikke noe frontend å peke på (se plan). Når frontend bygges, oppdater malen til en ekte URL. `locale` valideres allerede av Pydantic (Literal["nb","en"]) på vei inn i routeren, men faller trygt tilbake til "nb" her også hvis den skulle komme fra et annet kall uten den garantien. """ template = _TEMPLATES.get(locale, _TEMPLATES["nb"]) subject = template["subject"] body = template["body"].format(minutes=settings.MAGIC_LINK_MAX_AGE_MINUTES, token=raw_token) await to_thread(_send_sync, to_email, subject, body)