""" 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" "Åpne denne lenken for å logge inn på TeeCup innen {minutes} minutter:\n\n" "{link}\n\n" "Fungerer ikke lenken? Lim inn denne koden manuelt i stedet:\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" "Open this link to log in to TeeCup within {minutes} minutes:\n\n" "{link}\n\n" "Link not working? Paste this code in manually instead:\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 en klikkbar innloggingslenke (pluss selve koden som fallback). `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"] link = f"{settings.PUBLIC_BASE_URL}/verify?token={raw_token}" body = template["body"].format( minutes=settings.MAGIC_LINK_MAX_AGE_MINUTES, token=raw_token, link=link ) await to_thread(_send_sync, to_email, subject, body)