Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.0 KiB
Migration des emails transactionnels vers Brevo (API + templates hébergés)
Issue : #87
Branche : 87_brevo (partie de feature/73_compte_utilisateur)
Date : 2026-06-24
Contexte
Les emails transactionnels de decp.info (vérification d'adresse à l'inscription, réinitialisation de mot de passe) sont aujourd'hui envoyés via Flask-Mail + SMTP (boîte mail Infomaniak standard). Cette approche n'offre aucune visibilité sur la délivrabilité (le mail de reset est-il arrivé ?), expose au risque de suspension de la boîte mail en cas de pic, et délivre moins bien.
On migre vers Brevo (compte gratuit existant) en utilisant son API
transactionnelle via le SDK officiel brevo-python (v5) et ses templates
hébergés. Brevo est une société française, données en UE (bon pour le RGPD).
Objectif
Remplacer entièrement le transport SMTP/Flask-Mail par l'API Brevo, avec deux templates hébergés côté Brevo (un par email), sans changer l'interface publique du mailer ni le comportement des routes appelantes.
Périmètre
Dans le périmètre
- Réécriture de
src/auth/mailer.pypour utiliser le SDK Brevo. - Deux templates Brevo (déjà créés côté web), référencés par leur ID.
- Mise à jour des dépendances et des variables d'environnement.
- Suppression des templates Jinja d'email locaux.
- Réécriture des tests du mailer (hermétiques) + chemin sandbox optionnel.
Hors périmètre
- Le contenu/design des templates Brevo (géré dans l'interface web Brevo).
- Les autres usages d'email du projet s'il en existe (variables legacy
SENDER_SERVER_DOMAIN,LOGIN_EMAIL,FROM_EMAIL,TO_EMAILde.template.env) : à vérifier avant suppression ; ne pas y toucher si utilisées ailleurs.
Architecture
Interface publique inchangée
Les fonctions appelées depuis src/auth/routes.py (lignes 49 et 112) gardent
exactement leur signature — routes.py n'est pas modifié :
send_verification_email(email: str, token: str) -> None
send_reset_email(email: str, token: str) -> None
Composants de src/auth/mailer.py (réécrit)
| Fonction | Rôle |
|---|---|
init_mailer() |
Construit le client Brevo (TransactionalEmailsApi) depuis BREVO_API_KEY. Plus de paramètre app. Stocke le client au niveau module. |
_send_template(template_id, recipient, params) |
Construit un SendSmtpEmail(to, template_id, params, sender), applique le mode sandbox si activé, appelle send_transac_email, log + remonte les ApiException. |
send_verification_email(email, token) |
Construit le lien {base}/auth/verify-email?token=... et appelle _send_template(VERIFY_ID, email, {"link": link}). |
send_reset_email(email, token) |
Construit le lien {base}/reinitialiser-mot-de-passe?token=... et appelle _send_template(RESET_ID, email, {"link": link}). |
Disparaît : toute la logique Jinja (jinja_loader.searchpath, render_template,
les templates .txt/.html), MAIL_SUPPRESS_SEND, l'objet Flask-Mail.
Appel d'initialisation
src/auth/setup.py:39 passe de mailer.init_mailer(app) à mailer.init_mailer()
(seule modification hors mailer.py et tests).
Sender
Le sender (MAIL_FROM + MAIL_FROM_NAME) doit correspondre à un expéditeur
vérifié dans Brevo ; le domaine decp.info doit être authentifié (SPF/DKIM)
côté Brevo. Si le template Brevo définit déjà un sender, le passer explicitement
reste possible et prioritaire.
Données / flux
- Une route (
routes.py) génère un token et appellesend_*_email(email, token). - Le mailer construit le lien absolu (
APP_BASE_URL) et l'envoie commeparams = {"link": link}. - Le template Brevo correspondant (
template_id) injecte{{ params.link }}dans son HTML et envoie. - En cas d'échec API :
ApiExceptionloggée puis remontée (comme aujourd'huiflask_mail.sendpouvait lever).
Variables d'environnement (.template.env)
Ajoutées :
BREVO_API_KEY=— clé API transactionnelle BrevoBREVO_TEMPLATE_VERIFY_ID=— ID numérique du template de vérificationBREVO_TEMPLATE_RESET_ID=— ID numérique du template de resetBREVO_SANDBOX=—truepour valider sans délivrer (dev / intégration)MAIL_FROM_NAME=decp.info— nom d'expéditeur
Conservées : MAIL_FROM, APP_BASE_URL
Supprimées : SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD,
SMTP_USE_TLS, MAIL_SUPPRESS_SEND
Gestion d'erreur
_send_templateenveloppe l'appel dans untry/except ApiException: log (niveau error, sans la clé API) puisraisepour que l'appelant gère.- Si
init_mailer()n'a pas été appelé ouBREVO_API_KEYabsente :assert/erreur explicite, comme l'actuel « Mailer non initialisé ».
Stratégie de tests
Réconciliation validée :
- Tests unitaires (
tests/auth/test_mailer.py, réécrit) : on mockeTransactionalEmailsApi.send_transac_email(monkeypatch) et on vérifie le payload envoyé pour chaque fonction :to == [{"email": "a@b.c"}]template_id == BREVO_TEMPLATE_VERIFY_ID/..._RESET_IDparams["link"]contient le bon chemin et le token- Aucun appel réseau, tourne en CI sans clé.
- Mode sandbox Brevo : pour la vérification manuelle en dev —
BREVO_SANDBOX=trueajoute le header sandbox Brevo ; l'app accepte le déclenchement (signup/reset) et Brevo valide sans délivrer. - Test d'intégration optionnel :
@pytest.mark.integration, skippé par défaut, exécuté seulement siBREVO_API_KEYest présent, tape la vraie API en sandbox.
Points à vérifier en implémentation (ne pas présumer)
- Nom exact du package/import du SDK v5 (
brevo_pythonvs autre) et des classes (TransactionalEmailsApi,SendSmtpEmail,Configuration,ApiClient,ApiException) — confirmer sur la doc/repo getbrevo/brevo-python v5. - Mécanisme exact du mode sandbox Brevo (header
X-Sib-Sandboxet sa valeur, ou réglage compte) — confirmer côté doc Brevo. - Usage éventuel des variables legacy
SENDER_SERVER_DOMAIN/LOGIN_EMAIL/FROM_EMAIL/TO_EMAILailleurs dans le code avant toute suppression.
Critères de succès
- L'inscription envoie un email de vérification via Brevo (template hébergé) avec un lien fonctionnel.
- La demande de reset envoie l'email de reset via Brevo avec un lien fonctionnel.
routes.pyinchangé ; toute la suite de tests passe sans réseau.flask-mailet les configs/templates SMTP retirés du dépôt.