docs(brevo): spec de migration des emails transactionnels vers Brevo (#87)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
|||||||
|
# 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.py` pour 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_EMAIL` de `.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é** :
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
|
||||||
|
1. Une route (`routes.py`) génère un token et appelle `send_*_email(email, token)`.
|
||||||
|
2. Le mailer construit le lien absolu (`APP_BASE_URL`) et l'envoie comme
|
||||||
|
`params = {"link": link}`.
|
||||||
|
3. Le template Brevo correspondant (`template_id`) injecte `{{ params.link }}` dans
|
||||||
|
son HTML et envoie.
|
||||||
|
4. En cas d'échec API : `ApiException` loggée puis remontée (comme aujourd'hui
|
||||||
|
`flask_mail.send` pouvait lever).
|
||||||
|
|
||||||
|
## Variables d'environnement (`.template.env`)
|
||||||
|
|
||||||
|
**Ajoutées :**
|
||||||
|
|
||||||
|
- `BREVO_API_KEY=` — clé API transactionnelle Brevo
|
||||||
|
- `BREVO_TEMPLATE_VERIFY_ID=` — ID numérique du template de vérification
|
||||||
|
- `BREVO_TEMPLATE_RESET_ID=` — ID numérique du template de reset
|
||||||
|
- `BREVO_SANDBOX=` — `true` pour 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_template` enveloppe l'appel dans un `try/except ApiException` : log
|
||||||
|
(niveau error, sans la clé API) puis `raise` pour que l'appelant gère.
|
||||||
|
- Si `init_mailer()` n'a pas été appelé ou `BREVO_API_KEY` absente : `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 **mocke**
|
||||||
|
`TransactionalEmailsApi.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_ID`
|
||||||
|
- `params["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=true`
|
||||||
|
ajoute 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 si `BREVO_API_KEY` est 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_python` vs 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-Sandbox` et sa valeur,
|
||||||
|
ou réglage compte) — confirmer côté doc Brevo.
|
||||||
|
- Usage éventuel des variables legacy `SENDER_SERVER_DOMAIN`/`LOGIN_EMAIL`/
|
||||||
|
`FROM_EMAIL`/`TO_EMAIL` ailleurs 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.py` inchangé ; toute la suite de tests passe sans réseau.
|
||||||
|
- `flask-mail` et les configs/templates SMTP retirés du dépôt.
|
||||||
Reference in New Issue
Block a user