Vérification d'e-mails en Python
Ce tutoriel montre comment intégrer la vérification Mailbeam à une application web Python. Vous verrez des exemples pour FastAPI (asynchrone, recommandé pour un nouveau projet) et pour Flask (synchrone, pour une application existante).
Ce que vous allez construire
- Une dépendance FastAPI
validate_emailréutilisable - Une fonction utilitaire synchrone
verify_emailpour Flask ou tout autre framework synchrone - Une gestion d'erreurs correcte, qui ne bloque pas les utilisateurs quand l'API tombe
Prérequis
- Python 3.9 ou plus récent
- Un environnement virtuel (venv, poetry ou conda)
- Une clé d'API Mailbeam (inscription gratuite)
Étape 1 — Ajouter un petit client
Il n'y a pas de SDK à installer. Mailbeam se résume à un endpoint HTTP : un court module dans votre propre code suffit, et il n'y a rien à maintenir à jour. Des SDK officiels sont à la feuille de route ; voici ce qu'il faut faire aujourd'hui.
pip install httpx # ou requests, s'il est déjà dans le projet# app/mailbeam.py
import os
import httpx
ENDPOINT = "https://api.mailbeam.dev/v1/verify"
HEADERS = {"Authorization": f"Bearer {os.environ['MAILBEAM_KEY']}"}
class MailbeamError(Exception):
"""Tout ce qui nous a empêchés d'obtenir un verdict."""
async def verify_email(email: str, timeout: float = 3.0) -> dict:
try:
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(ENDPOINT, json={"email": email}, headers=HEADERS)
response.raise_for_status()
return response.json()
except httpx.HTTPError as exc:
raise MailbeamError(str(exc)) from exc
def verify_email_sync(email: str, timeout: float = 3.0) -> dict:
try:
response = httpx.post(ENDPOINT, json={"email": email}, headers=HEADERS, timeout=timeout)
response.raise_for_status()
return response.json()
except httpx.HTTPError as exc:
raise MailbeamError(str(exc)) from excÉtape 2 — Déclarer votre variable d'environnement
# .env
MAILBEAM_KEY=mb_live_xxxxxxxxxxxxxxxxxxxxChargez-la avec python-dotenv ou la méthode privilégiée par votre framework :
import os
from dotenv import load_dotenv
load_dotenv()
MAILBEAM_KEY = os.environ["MAILBEAM_KEY"]Étape 3 — FastAPI : créer la dépendance de validation
# app/dependencies.py
import logging
from fastapi import HTTPException
from app.mailbeam import MailbeamError, verify_email
async def validate_email(email: str) -> str:
"""
Dépendance FastAPI qui vérifie une adresse e-mail.
Lève un HTTP 422 si l'adresse est invalide ou de faible qualité.
Échoue en mode permissif (laisse passer) sur une erreur de l'API Mailbeam.
"""
try:
result = await verify_email(email)
except MailbeamError as exc:
# On journalise sans bloquer — notre panne n'est pas le problème de l'utilisateur.
logging.error("Échec de la vérification Mailbeam : %s", exc)
return email
if not result["valid"] or result["score"] < 60:
raise HTTPException(
status_code=422,
detail={
"error": "Merci de fournir une adresse e-mail valide.",
"code": result["reason"] or "invalid_email",
},
)
return emailÉtape 4 — L'ajouter à votre endpoint d'inscription
# app/routes/auth.py
from fastapi import APIRouter, Depends
from pydantic import BaseModel, EmailStr
from typing import Annotated
from app.dependencies import validate_email
router = APIRouter()
class SignupRequest(BaseModel):
email: EmailStr
password: str
@router.post("/signup", status_code=201)
async def signup(
request: SignupRequest,
# La dépendance s'exécute AVANT le handler et lève un 422 si l'adresse est invalide
verified_email: Annotated[str, Depends(validate_email)],
):
user = await create_user(email=verified_email, password=request.password)
return {"user": user}Votre application principale :
# app/main.py
from fastapi import FastAPI
from app.routes.auth import router
app = FastAPI()
app.include_router(router, prefix="/api/auth")Étape 5 — Version Flask (synchrone)
# app/utils/email.py
import logging
from app.mailbeam import MailbeamError, verify_email_sync
def check_email(email: str) -> tuple[bool, str | None]:
"""
Renvoie (is_valid, reason_code).
Échoue en mode permissif (renvoie True) sur une erreur d'API.
"""
try:
result = verify_email_sync(email)
except MailbeamError as exc:
logging.error("Échec de la vérification Mailbeam : %s", exc)
return True, None # échec permissif
if not result["valid"] or result["score"] < 60:
return False, result["reason"] or "invalid_email"
return True, None
# app/routes/auth.py (Flask)
from flask import Blueprint, request, jsonify
from app.utils.email import check_email
bp = Blueprint("auth", __name__)
@bp.post("/api/auth/signup")
def signup():
data = request.get_json()
email = data.get("email", "")
is_valid, reason = verify_email_sync(email)
if not is_valid:
return jsonify({"error": "Merci de fournir une adresse e-mail valide.", "code": reason}), 422
user = create_user(email=email, password=data.get("password"))
return jsonify({"user": user}), 201Tester l'intégration
# tests/test_signup.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_signup_with_valid_email():
# Utilise le domaine de test de Mailbeam : toujours valide, sans entamer le quota
response = client.post(
"/api/auth/signup",
json={"email": "user@valid.mailbeam-test.dev", "password": "pass1234"},
)
assert response.status_code == 201
def test_signup_with_invalid_email():
response = client.post(
"/api/auth/signup",
json={"email": "user@invalid.mailbeam-test.dev", "password": "pass1234"},
)
assert response.status_code == 422
assert response.json()["detail"]["code"] is not None
def test_signup_with_disposable_email():
response = client.post(
"/api/auth/signup",
json={"email": "temp@disposable.mailbeam-test.dev", "password": "pass1234"},
)
assert response.status_code == 422Ajouter une couche de cache
# app/dependencies.py
from functools import lru_cache
import asyncio
# Cache TTL simple en mémoire, sur un dict (en production, utilisez Redis)
_cache: dict[str, tuple] = {}
async def validate_email(email: str) -> str:
normalized = email.lower().strip()
if normalized in _cache:
valid, score, reason = _cache[normalized]
if not valid or score < 60:
raise HTTPException(
status_code=422,
detail={"error": "Merci de fournir une adresse e-mail valide.", "code": reason},
)
return email
try:
result = await verify_email(normalized)
except MailbeamError:
return email # échec permissif
_cache[normalized] = (result["valid"], result["score"], result["reason"])
if not result["valid"] or result["score"] < 60:
raise HTTPException(
status_code=422,
detail={"error": "Merci de fournir une adresse e-mail valide.", "code": result["reason"]},
)
return emailBonnes pratiques
| Pratique | Pourquoi |
|---|---|
| Utiliser le client asynchrone avec FastAPI | Ne bloque pas la boucle d'événements |
| Utiliser le client synchrone avec Flask | Pas de surcoût asynchrone dans un framework synchrone |
Échec permissif sur MailbeamError | Une panne d'API ne doit pas casser l'inscription |
| Normaliser l'adresse avant de la mettre en cache | Évite les ratés de cache dus à la casse |
| Utiliser les domaines de test dans pytest | Tests isolés, sans consommation de quota |
Liste de contrôle avant la mise en production
-
MAILBEAM_KEYdans les secrets d'environnement, pas dans le code - Journalisation des erreurs configurée (
logging.error, pasprint) - Cache configuré (Redis pour les déploiements multi-workers)
- Domaines de test utilisés dans les fixtures
pytest - Message d'erreur 422 lisible pour l'utilisateur côté front-end