""" Brevo (ex-Sendinblue) integration. - Send transactional confirmation emails - Sync confirmed contacts to a Brevo list """ import logging import httpx from app.config import settings logger = logging.getLogger(__name__) BREVO_API = "https://api.brevo.com/v3" # ── Translations for transactional emails ────────────────────────────── _EMAIL_I18N: dict[str, dict[str, str]] = { "en": { "subject": "Confirm your spot on the adiuvAI waitlist", "preheader": "Confirm your email to secure your early access spot on the adiuvAI waitlist.", "badge": "One more step", "heading": "Confirm your spot on
the waitlist", "body": "Thanks for signing up! Please confirm your email address so we can keep you in the loop when adiuvAI launches.", "cta": "Confirm my email", "expiry": "This link expires in {hours} hours.", "footer": "If you didn't sign up, simply ignore this email.", }, "it": { "subject": "Conferma il tuo posto nella lista d'attesa di adiuvAI", "preheader": "Conferma la tua email per assicurarti un accesso anticipato ad adiuvAI.", "badge": "Ancora un passaggio", "heading": "Conferma il tuo posto
nella lista d'attesa", "body": "Grazie per esserti iscritto! Conferma il tuo indirizzo email così potremo aggiornarti quando adiuvAI sarà disponibile.", "cta": "Conferma la mia email", "expiry": "Questo link scade tra {hours} ore.", "footer": "Se non ti sei iscritto, ignora semplicemente questa email.", }, "es": { "subject": "Confirma tu lugar en la lista de espera de adiuvAI", "preheader": "Confirma tu correo para asegurar tu acceso anticipado a adiuvAI.", "badge": "Un paso más", "heading": "Confirma tu lugar en
la lista de espera", "body": "¡Gracias por registrarte! Confirma tu dirección de correo para que podamos avisarte cuando adiuvAI esté listo.", "cta": "Confirmar mi correo", "expiry": "Este enlace caduca en {hours} horas.", "footer": "Si no te registraste, simplemente ignora este correo.", }, "fr": { "subject": "Confirmez votre place sur la liste d'attente d'adiuvAI", "preheader": "Confirmez votre e‑mail pour sécuriser votre accès anticipé à adiuvAI.", "badge": "Encore une étape", "heading": "Confirmez votre place sur
la liste d'attente", "body": "Merci de vous être inscrit ! Veuillez confirmer votre adresse e‑mail pour que nous puissions vous tenir informé du lancement d'adiuvAI.", "cta": "Confirmer mon e‑mail", "expiry": "Ce lien expire dans {hours} heures.", "footer": "Si vous ne vous êtes pas inscrit, ignorez simplement cet e‑mail.", }, "de": { "subject": "Bestätige deinen Platz auf der adiuvAI-Warteliste", "preheader": "Bestätige deine E‑Mail, um dir den frühen Zugang zu adiuvAI zu sichern.", "badge": "Noch ein Schritt", "heading": "Bestätige deinen Platz
auf der Warteliste", "body": "Danke für deine Anmeldung! Bitte bestätige deine E‑Mail-Adresse, damit wir dich informieren können, wenn adiuvAI startet.", "cta": "Meine E‑Mail bestätigen", "expiry": "Dieser Link läuft in {hours} Stunden ab.", "footer": "Falls du dich nicht angemeldet hast, ignoriere diese E‑Mail einfach.", }, } def _t(lang: str, key: str) -> str: """Get translated string, falling back to English.""" return _EMAIL_I18N.get(lang, _EMAIL_I18N["en"]).get(key, _EMAIL_I18N["en"][key]) def _headers() -> dict[str, str]: return { "api-key": settings.BREVO_API_KEY, "Content-Type": "application/json", "Accept": "application/json", } async def send_confirmation_email(email: str, confirm_url: str, unsubscribe_url: str = "", lang: str = "en") -> bool: """Send a double opt-in confirmation email. Returns True on success.""" if not settings.brevo_configured: logger.warning("Brevo not configured — skipping confirmation email for %s***", email[:3]) return False payload = { "sender": { "name": settings.BREVO_SENDER_NAME, "email": settings.BREVO_SENDER_EMAIL, }, "to": [{"email": email}], "subject": _t(lang, "subject"), "htmlContent": _confirmation_html(confirm_url, unsubscribe_url, lang), } try: async with httpx.AsyncClient(timeout=10) as client: resp = await client.post(f"{BREVO_API}/smtp/email", headers=_headers(), json=payload) resp.raise_for_status() logger.info("Confirmation email sent to %s***", email[:3]) return True except httpx.HTTPError: logger.exception("Failed to send confirmation email to %s***", email[:3]) return False async def add_contact_to_list(email: str) -> bool: """Add a confirmed contact to the Brevo waitlist list. Returns True on success.""" if not settings.brevo_configured: logger.warning("Brevo not configured — skipping contact sync for %s***", email[:3]) return False if settings.BREVO_LIST_ID == 0: logger.warning("BREVO_LIST_ID not set — skipping contact sync") return False payload = { "email": email, "listIds": [settings.BREVO_LIST_ID], "updateEnabled": True, } try: async with httpx.AsyncClient(timeout=10) as client: resp = await client.post(f"{BREVO_API}/contacts", headers=_headers(), json=payload) resp.raise_for_status() logger.info("Contact synced to Brevo list %d: %s***", settings.BREVO_LIST_ID, email[:3]) return True except httpx.HTTPError: logger.exception("Failed to sync contact to Brevo: %s***", email[:3]) return False def _confirmation_html(confirm_url: str, unsubscribe_url: str = "", lang: str = "en") -> str: """Email template aligned with the adiuvAI landing page brand.""" html_lang = lang if lang in ("en", "it", "es", "fr", "de") else "en" badge = _t(lang, "badge") heading = _t(lang, "heading") body = _t(lang, "body") cta = _t(lang, "cta") expiry = _t(lang, "expiry").format(hours=settings.CONFIRM_TOKEN_EXPIRY_HOURS) preheader = _t(lang, "preheader") footer = _t(lang, "footer") unsub_label = {"en": "Unsubscribe", "it": "Annulla iscrizione", "es": "Cancelar suscripción", "fr": "Se désabonner", "de": "Abmelden"}.get(lang, "Unsubscribe") return f"""\ {_t(lang, "subject")} — adiuvAI
{preheader}
 
adiuvAI
● {badge}

{heading}

{body}

{cta}

{expiry}

 

{footer}

adiuvai.com {f' · {unsub_label}' if unsubscribe_url else ''}

"""