Comment vérifier des adresses e-mail en Python

Vérifier une adresse e-mail en Python est simple pour les parties faciles, et étonnamment difficile pour celle qui compte le plus. Ce guide présente les vérifications de syntaxe et les recherches DNS que vous pouvez exécuter en local, explique précisément pourquoi les sondes SMTP brutes échouent en pratique, et montre comment obtenir un résultat au niveau de la boîte mail avec une API REST.

À retenir

Utilisez Python pour rejeter les saisies mal formées et, si besoin, mettre en cache les vérifications DNS/MX. Utilisez une API de vérification pour le résultat au niveau de la boîte mail, car les sondes smtplib brutes sont faciles à brider et renvoient souvent des réponses ambiguës.

Ce que "vérifier" un e-mail veut vraiment dire

La vérification d'emails consiste à confirmer qu'une adresse est réelle et peut recevoir des e-mails avant de lui envoyer quoi que ce soit. Elle se décompose en une suite de vérifications, chacune plus difficile que la précédente : l'adresse est-elle correctement formée, son domaine accepte-t-il des e-mails, et la boîte précise existe-t-elle ? En Python, les deux premières se font bien et facilement. La troisième est celle où la plupart des solutions maison échouent sans bruit, et comprendre pourquoi est l'élément le plus utile de cet article.

Le plan ci-dessous fonctionne par couches. Exécutez en local les vérifications rapides et peu coûteuses pour écarter les saisies manifestement erronées, puis appelez une API de vérification pour savoir, au niveau de la boîte mail, si l'adresse est utilisable. Vous rejetez ainsi instantanément les saisies aberrantes sans appel API, et obtenez un résultat plus clair pour tout ce qui passe ce premier filtre.

Contrôles de vérification d'e-mails en couches avec Python : validation de syntaxe, recherche DNS MX, vérification par API et règles applicatives
Une implémentation Python fiable garde les vérifications locales rapides et peu coûteuses, puis demande à une API de vérification gérée les verdicts SMTP, catch-all, jetable et générique.

Étape 1 : validation de la syntaxe

La première vérification confirme que l'adresse est correctement formée. Il est tentant d'écrire une expression régulière pour cela, mais la syntaxe des e-mails est bien plus complexe qu'il n'y paraît, et les regex faites maison sont presque toujours trop strictes ou trop permissives. La bibliothèque de référence pour cette tâche est email-validator, qui implémente correctement les normes concernées et est largement utilisée en production.

Installez-la avec pip :

pip install email-validator

Puis validez une adresse. La bibliothèque lève EmailNotValidError pour toute adresse mal formée et renvoie un objet normalisé pour une saisie valide :

from email_validator import validate_email, EmailNotValidError

def check_syntax(address):
    try:
        result = validate_email(address, check_deliverability=False)
        return True, result.normalized
    except EmailNotValidError as error:
        return False, str(error)

ok, value = check_syntax("jane.doe@example.com")
print(ok, value)  # True jane.doe@example.com

ok, value = check_syntax("jane.doe@@example")
print(ok, value)  # False, with a description of the problem

Définir check_deliverability=False limite cette étape à la syntaxe pure, sans aucun appel réseau : elle est donc assez rapide pour s'exécuter à chaque frappe ou à chaque envoi de formulaire. Stockez toujours la valeur normalized renvoyée par la bibliothèque plutôt que la saisie brute de l'utilisateur, car elle canonise l'adresse pour un stockage cohérent.

Étape 2 : vérifications du domaine et des MX

Une adresse correctement formée reste inutile si son domaine ne peut pas recevoir d'e-mails. La vérification suivante interroge les enregistrements DNS du domaine à la recherche d'enregistrements MX, qui désignent les serveurs de messagerie responsables de ce domaine. Un domaine sans enregistrement MX ne peut pas recevoir de courrier : toute adresse sur ce domaine peut donc être rejetée immédiatement.

Le email-validator peut s'en charger pour vous si vous passez check_deliverability=True, ce qui effectue la recherche MX pendant la validation :

from email_validator import validate_email, EmailNotValidError

def check_domain(address):
    try:
        result = validate_email(address, check_deliverability=True)
        return True, result.domain
    except EmailNotValidError as error:
        return False, str(error)

Si vous voulez exécuter vous-même la requête DNS, par exemple pour mettre en cache les résultats de nombreuses adresses du même domaine, utilisez directement la bibliothèque dnspython pour cela :

pip install dnspython
import dns.exception
import dns.resolver

def has_mx_record(domain):
    try:
        records = dns.resolver.resolve(domain, "MX")
        return len(records) > 0
    except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
        return False
    except dns.exception.DNSException:
        return False  # Treat resolver errors as inconclusive.

print(has_mx_record("gmail.com"))  # True
print(has_mx_record("no-such-domain-xyz.invalid"))  # False

C'est vraiment utile. Cela élimine les adresses sur des domaines expirés, inexistants ou qui ne gèrent tout simplement pas le courrier. Mais remarquez ce que cette étape ne vous a toujours pas dit : si someone@gmail.com est une vraie boîte mail. Gmail possède des enregistrements MX, donc toutes les adresses Gmail passent cette vérification, valides ou non.

Étape 3 : la vérification de la boîte mail, et pourquoi le SMTP brut ne suffit pas

Confirmer la boîte elle-même exige de dialoguer avec le serveur de messagerie destinataire. En principe, vous vous connectez en SMTP, vous vous identifiez, envoyez une commande RCPT TO avec l'adresse cible et lisez le code de réponse : un code positif suggère que la boîte existe, un refus suggère qu'elle n'existe pas. Le module intégré de Python smtplib peut le faire, et le code paraît trompeusement simple :

import smtplib
import dns.resolver

def naive_smtp_check(address):
    domain = address.split("@")[1]
    mx_host = str(dns.resolver.resolve(domain, "MX")[0].exchange)

    server = smtplib.SMTP(timeout=10)
    server.connect(mx_host)
    server.helo("example.com")
    server.mail("probe@example.com")
    code, message = server.rcpt(address)
    server.quit()

    # 250 and 251 generally mean the mailbox was accepted.
    return code in (250, 251)

Cela fonctionne dans un tutoriel et échoue en production. Les raisons valent d'être connues en détail, car elles expliquent pourquoi aucun vérificateur sérieux ne s'appuie sur un script de ce type.

Réputation IP et bridage

Les fournisseurs de messagerie détectent activement les sondes de vérification. Lancez quelques vérifications RCPT TO depuis un seul serveur, et des fournisseurs comme Gmail et Outlook brideront ou bloqueront cette IP en quelques tentatives. Une fois bridé, le serveur renvoie des réponses ambiguës, que la boîte existe ou non, et vos résultats perdent tout sens. C'est précisément pour cette raison que la vraie vérification s'effectue depuis de grands pools d'adresses IP correctement préchauffées et bien gérées.

Greylisting

De nombreux serveurs pratiquent le greylisting, en renvoyant volontairement un échec temporaire 4xx la première fois qu'un expéditeur inconnu prend contact. Les serveurs de messagerie légitimes réessaient plus tard ; les spammeurs, en général, non. Un script naïf qui ne réessaie pas interprète cet échec temporaire comme un refus et marque comme invalide une boîte parfaitement valide. C'est un faux négatif dû uniquement à l'impatience.

Domaines catch-all

Un domaine catch-all accepte le courrier pour toutes les adresses possibles, donc RCPT TO renvoie un code positif pour realperson@domain.com et le random-string-9f3a@domain.com sans distinction. Une vérification SMTP brute ne peut pas distinguer une vraie boîte d'une boîte inexistante sur ces domaines. Détecter un catch-all exige d'envoyer une seconde sonde vers une adresse qui n'existe certainement pas et de voir si elle est acceptée aussi : c'est de la détection catch-all, pas un simple oui ou non.

Réponses ambiguës des grands fournisseurs

Chez certains grands fournisseurs, la réponse à RCPT TO est délibérément ambiguë. Certains acceptent toutes les adresses au stade de la poignée de main et ne génèrent le rebond que plus tard : la réponse SMTP ne contient tout simplement pas l'information que vous cherchez.

C'est pourquoi les mainteneurs de email-validator excluent délibérément les vérifications SMTP de la bibliothèque. Leur documentation indique clairement qu'il n'y a rien à gagner à contacter soi-même un serveur SMTP, car les serveurs savent très bien ne pas révéler si une adresse est délivrable, et une adresse qui semble accepter le courrier peut tout de même rebondir ensuite. La conclusion honnête : la syntaxe et les MX, vous pouvez bien les gérer dans votre propre code, mais pas une vérification fiable de la boîte mail.

Étape 4 : vérifier avec l'API VeriMails

La solution pratique consiste à confier la vérification de la boîte mail à un service de vérification, depuis une infrastructure conçue pour cela, et à appeler ce service depuis Python avec une seule requête HTTP. VeriMails propose une API REST qui exécute toute la chaîne : syntaxe, MX, DNS, poignée de main SMTP en direct, détection des catch-all, détection des adresses jetables et génériques, et renvoie un résultat structuré unique.

Avec la bibliothèque standard requests , un appel de vérification ressemble à ceci. Pour le détail du point de terminaison, consultez la Documentation de l'API VeriMails:

import requests

def verify_email(address, api_key):
    response = requests.get(
        "https://api.verimails.com/v1/verify",
        params={"email": address},
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

result = verify_email("jane.doe@example.com", api_key="YOUR_API_KEY")
print(result["status"])      # e.g. "deliverable", "undeliverable", "catch_all"
print(result["disposable"])  # True if a throwaway address
print(result["role_based"])  # True for addresses such as info@ or support@

Vous pouvez ensuite agir selon le résultat. Un formulaire d'inscription peut accepter les adresses deliverable , rejeter les adresses undeliverable et le disposable et définir sa propre politique pour les résultats catch_all:

def is_acceptable_for_signup(address, api_key):
    result = verify_email(address, api_key)

    if result["disposable"]:
        return False, "Disposable addresses are not allowed."
    if result["status"] == "undeliverable":
        return False, "That address does not appear to exist."

    return True, "Address verified."

Comme chaque appel renvoie un résultat clair, il est assez rapide pour être exécuté pendant l'envoi d'un formulaire sans faire attendre l'utilisateur. Pour une liste d'adresses déjà présente dans votre base, vérification CSV en masse est plus adaptée, car importer un seul fichier est bien plus efficace que d'appeler le point de terminaison unitaire des milliers de fois en boucle.

Le schéma recommandé pour une application Python combine les deux couches : conservez la vérification de syntaxe locale avec email-validator pour rejeter instantanément les saisies aberrantes avant de dépenser un crédit, puis appelez l'API pour obtenir le résultat de délivrabilité de référence sur tout ce qui passe. Vous obtenez un retour rapide sur les erreurs évidentes et un verdict fiable pour les vraies adresses.

Tableau des politiques applicatives

RésultatPolitique à l'inscriptionPolitique d'import ou CRM
deliverableAccepter et stocker l'adresse normaliséeConserver dans le segment actif
undeliverableRejeter avec un message de correction clairExclure avant toute campagne
catch_allAccepter ou examiner selon le risque du compteSegmenter à part et envoyer avec prudence
disposableRejeter pour la plupart des produitsExclure des listes de prospection
role_basedAutoriser uniquement si les boîtes partagées sont acceptablesExaminer avant la prospection à froid

Appels API ou traitements de listes

Utilisez l'API là où une décision immédiate compte : inscription, paiement, formulaires de leads, invitations et modifications de compte. Utilisez la vérification en masse pour un export CSV, un nettoyage de CRM ou une audience de campagne où des milliers d'adresses doivent passer par la même revue. Les deux workflows doivent alimenter les mêmes règles d'exclusion, pour qu'une adresse rejetée par un canal ne soit pas contactée plus tard depuis un autre outil.

Tout assembler

Une implémentation propre comporte trois couches. D'abord, une email-validator vérification de syntaxe avec check_deliverability=False, exécutée localement et instantanément pour rejeter les saisies mal formées. Ensuite, une vérification MX optionnelle avec dnspython si vous voulez filtrer les domaines morts avant d'appeler l'API, même si l'API couvre aussi ce point. Enfin, un appel à l'API VeriMails pour le résultat au niveau de la boîte mail, y compris la détection des catch-all, des adresses jetables et génériques, que vous ne pouvez pas reproduire de façon fiable par vous-même.

Ce qu'il faut retenir, c'est la répartition des rôles. La syntaxe et le DNS, vous pouvez vraiment les gérer en Python. La vérification de la boîte en direct dépend de la réputation IP, du comportement de greylisting et de la détection catch-all, qu'un script isolé ne peut pas maîtriser : c'est pourquoi la bibliothèque de validation de référence exclut délibérément les sondes SMTP. Utilisez les vérifications locales pour la rapidité, une API pour la vérification au niveau de la boîte mail, et les page des tarifs pour choisir la formule adaptée à votre volume prévu.

Questions fréquentes

Vous pouvez contrôler la syntaxe avec la bibliothèque email-validator et confirmer qu'un domaine possède des enregistrements MX avec dnspython, entièrement dans votre code. Ce que vous ne pouvez pas faire de façon fiable seul, c'est confirmer que la boîte existe. Cela exige une poignée de main SMTP en direct depuis des adresses IP de confiance, et les sondes smtplib brutes lancées depuis un seul serveur sont généralement bloquées ou bridées par les fournisseurs de messagerie.
Les grands fournisseurs détectent les sondes de vérification et brident l'IP source en quelques tentatives, puis renvoient des réponses ambiguës, que la boîte existe ou non. Le greylisting renvoie un échec temporaire aux expéditeurs inconnus, qu'un script naïf interprète à tort comme invalide. Les domaines catch-all acceptent toutes les adresses. La bibliothèque officielle email-validator omet délibérément le SMTP pour ces raisons.
Adoptez une approche en couches : exécutez en local une vérification de syntaxe rapide pour rejeter les saisies manifestement erronées, puis appelez une API de vérification comme VeriMails pour le résultat au niveau de la boîte mail. L'API effectue la poignée de main SMTP depuis une infrastructure correctement gérée et renvoie les résultats syntaxe, MX, DNS, SMTP, catch-all, jetable et générique en une seule réponse.
L'API VeriMails renvoie un résultat clair pour chaque adresse, assez rapidement pour être appelée pendant l'envoi d'un formulaire. Les nouveaux comptes reçoivent 100 crédits offerts, sans carte bancaire, pour tester l'intégration avant de payer quoi que ce soit.

Partez de données propres

100 crédits gratuits à l'inscription. Sans carte bancaire. Mettez ces conseils en pratique dès aujourd'hui.

Commencer gratuitement
Sans carte bancaire. Les crédits n'expirent jamais.