Email : ne plus journaliser l'adresse du destinataire en cas de refus SMTP

En cherchant pourquoi les logs d'envoi d'email semblaient invisibles, on a
plutôt trouvé un problème RGPD : `SMTPRecipientsRefused` embarque l'adresse
du destinataire dans ses `args` (`{adresse: (code, message)}`), et
`logger.error(..., exc)` la journalisait telle quelle via `str(exc)` — en
violation directe de la règle « pas de log des noms/emails/téléphones ».

`_smtp_error_summary()` n'en extrait que les codes/messages serveur.

Constaté au passage (pas un bug) : le mécanisme de logs lui-même fonctionne
bien — `docker compose logs -f backend` affiche bien les lignes `app.email`
dès qu'une vraie requête HTTP déclenche l'envoi (testé en direct sur
l'édition de démo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-15 19:23:21 +02:00
co-authored by Claude Sonnet 5
parent d5f67b2701
commit fcc42c8d43
2 changed files with 63 additions and 1 deletions
+17 -1
View File
@@ -16,6 +16,22 @@ from app.config import Settings, get_settings
logger = logging.getLogger("app.email")
def _smtp_error_summary(exc: smtplib.SMTPException) -> str:
"""Résumé RGPD-safe d'une erreur SMTP : codes/messages serveur seulement.
Certaines exceptions de `smtplib` embarquent l'adresse du destinataire
dans leurs `args` (notamment `SMTPRecipientsRefused.recipients`, un
`{adresse: (code, message)}`) — `str(exc)` les journaliserait telles
quelles. On n'en garde que les codes/messages, jamais les adresses.
"""
if isinstance(exc, smtplib.SMTPRecipientsRefused):
codes = sorted({code for code, _msg in exc.recipients.values()})
return f"destinataire(s) refusé(s), code(s) {codes}"
if isinstance(exc, smtplib.SMTPResponseException):
return f"{exc.smtp_code} {exc.smtp_error!r}"
return exc.__class__.__name__
def build_message(to: str, subject: str, body: str) -> EmailMessage:
message = EmailMessage()
message["From"] = get_settings().smtp_from
@@ -84,7 +100,7 @@ def deliver_many(messages: list[EmailMessage]) -> None:
logger.error(
"Email refusé par le serveur (From=%s) : %s",
message["From"],
exc,
_smtp_error_summary(exc),
)
# RGPD : ni destinataire ni corps, seulement le compte et le sujet.
logger.info(