Bêta privéeMailbeam est en bêta privée : l'API publique n'est pas encore ouverte.Rejoindre la liste d'attente

Mailbeam
Python + FastAPIDébutant15 minutesMis à jour en janvier 2025

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_email réutilisable
  • Une fonction utilitaire synchrone verify_email pour 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_xxxxxxxxxxxxxxxxxxxx

Chargez-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}), 201

Tester 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 == 422

Ajouter 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 email

Bonnes pratiques

PratiquePourquoi
Utiliser le client asynchrone avec FastAPINe bloque pas la boucle d'événements
Utiliser le client synchrone avec FlaskPas de surcoût asynchrone dans un framework synchrone
Échec permissif sur MailbeamErrorUne 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 pytestTests isolés, sans consommation de quota

Liste de contrôle avant la mise en production

  • MAILBEAM_KEY dans les secrets d'environnement, pas dans le code
  • Journalisation des erreurs configurée (logging.error, pas print)
  • 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

Prochaines étapes