Skip to content
Commencez avec 100 crédits de vérification gratuits
Qualisend
Tous les articles
Ingénierie / 11 juin 2026

Comment valider une adresse e-mail dans Django

8 minutes read

Qualisend team
Fenêtre de code intitulée validators.py montrant trois couches de validation Django qui affinent progressivement — validate_email, une résolution MX et un POST vers l'API de vérification — se terminant par un badge vert « deliverable ».

Pour valider une adresse e-mail dans Django, vous effectuez trois contrôles, pas un seul — et le framework ne fournit que le premier. Les validateurs de Django confirment qu'une adresse a la bonne forme ; ils ne se demandent jamais si son domaine peut recevoir du courrier ni si la boîte aux lettres existe. La vraie validation se fait par couches : la syntaxe, puis une résolution DNS, puis une sonde SMTP de la boîte aux lettres. Ce guide construit chaque couche sous la forme d'un validateur Django que vous pouvez glisser dans un formulaire ou un sérialiseur Django REST Framework, et montre où une API de vérification prend le relais. Il reprend là où s'arrête le guide Python, de sorte que le raisonnement sur le DNS et le SMTP qui s'y trouve reste entièrement valable.

La réponse courte#

Utilisez validate_email (ou n'importe quel EmailField) pour la syntaxe, dnspython pour la résolution MX, et une API de vérification pour la vérification de boîte aux lettres SMTP — câblés comme trois validateurs, du moins coûteux au plus coûteux, s'interrompant dès que l'un d'eux est décisif. N'ouvrez pas de connexions SMTP depuis votre processus Django pour sonder les boîtes aux lettres : le port 25 sortant est bloqué sur la plupart des hôtes, et la réponse dépend de la réputation de l'IP d'envoi et du greylisting que vous n'avez pas envie de réimplémenter. Chaque couche élimine des adresses de manière plus économique que la précédente ; seule l'API peut en valider une. Si ce pipeline vous est nouveau, ce qu'est la vérification d'e-mail définit d'abord les termes.

Couche 1 : validation du format avec les validateurs de Django#

Django gère déjà la première couche. django.core.validators.validate_email est une instance de EmailValidator qui lève une ValidationError sur une adresse malformée et renvoie None sur une adresse correcte :

from django.core.validators import validate_email
from django.core.exceptions import ValidationError

def is_valid_syntax(email: str) -> bool:
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

Vous l'appelez rarement à la main. Chaque forms.EmailField, models.EmailField et serializers.EmailField de DRF attache automatiquement un EmailValidator, si bien que déclarer un champ vous offre gratuitement la validation de syntaxe :

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # EmailValidator runs during clean()

Sachez exactement ce que cela vous apporte. EmailValidator vérifie la forme par rapport à une grammaire dérivée des RFC — il ne résout jamais le DNS, n'ouvre jamais de socket et n'a aucune idée de l'existence d'example.com. definitely-fake@gmail.com passe. info@company-that-folded.com passe. typo@gmial.com passe. Ces trois adresses sont indélivrables, et aucun validateur qui se contente de lire la chaîne ne vous le dira jamais, pour la même raison qui fait que la validation d'e-mail par expression régulière échoue : la syntaxe et la délivrabilité sont deux questions distinctes. L'une est un fait à propos de la chaîne ; l'autre est un fait à propos d'Internet.

Couche 2 : le domaine accepte-t-il le courrier ?#

C'est la première couche que Django ne vous fournit pas, et elle est peu coûteuse à ajouter. Un domaine sans enregistrement MX ne peut recevoir de courrier pour personne ; une seule résolution DNS élimine donc les domaines morts, les noms d'entreprise mal orthographiés et les TLD inventés. Django n'a pas de résolveur MX intégré ; tournez-vous donc vers dnspython et enveloppez-le dans une simple fonction :

import dns.resolver  # pip install dnspython

def has_mail_route(domain: str) -> bool:
    try:
        return len(dns.resolver.resolve(domain, "MX")) > 0
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
        return False

Un validateur Django n'est qu'un appelable qui lève une ValidationError sur une entrée invalide ; promouvez donc ce contrôle en validateur et il s'insère directement dans la liste validators de n'importe quel champ :

from django.core.exceptions import ValidationError

def validate_deliverable_domain(email: str) -> None:
    domain = email.rsplit("@", 1)[-1]
    if not has_mail_route(domain):
        raise ValidationError("This domain can't receive email.")
validate_deliverable_domain("jane@gmail.com")               # passes
validate_deliverable_domain("jane@company-that-folded.com") # raises ValidationError

Si vous souhaitez honorer les domaines qui acceptent le courrier sur un enregistrement A sans MX, repliez-vous sur la résolution de A/AAAA lorsque l'ensemble MX est vide — mais un contrôle MX couvre la grande majorité des adresses réelles.

Couche 3 : la boîte aux lettres existe-t-elle réellement ?#

Les couches un et deux ne peuvent qu'éliminer une adresse. Un domaine peut publier des enregistrements MX parfaits et n'avoir malgré tout aucune boîte aux lettres à l'adresse que vous détenez — noreply-9f2x@gmail.com a une syntaxe valide sur une route de messagerie active, et c'est pourtant une boîte aux lettres qui n'a jamais été créée. Confirmer une boîte aux lettres précise implique la conversation de livraison SMTP : se connecter à l'hôte de messagerie, émettre un RCPT TO, lire la réponse et se déconnecter avant d'envoyer quoi que ce soit.

En principe, vous pourriez scripter cela depuis Django avec smtplib. En pratique, vous ne devriez pas l'exécuter depuis votre serveur applicatif : la plupart des hôtes cloud bloquent le port 25 sortant, la réponse dépend de la réputation de l'IP depuis laquelle vous vous connectez, et les serveurs de réception appliquent du greylisting et limitent le débit des expéditeurs inconnus — de sorte qu'une sonde qui fonctionne dans un test local échoue discrètement, ou vous fait blacklister, en production. Comment fonctionne la vérification d'e-mail parcourt l'ensemble du pipeline, domaines catch-all compris. C'est la couche qu'il vaut la peine de déléguer.

Le point de terminaison de vérification de Qualisend exécute tout le pipeline — syntaxe, DNS et sonde SMTP de la boîte aux lettres — depuis une infrastructure à réputation gérée, et renvoie un verdict. Appelez-le depuis un validateur avec requests, en lisant votre clé depuis l'environnement :

import os
import requests  # pip install requests

QUALISEND_VERIFY_URL = "https://api.qualisend.com/v1/verify"

def verify(email: str) -> dict:
    res = requests.post(
        QUALISEND_VERIFY_URL,
        headers={"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"},
        json={"email": email},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["result"]  # {"status", "score", "reason", "sub_flags"}

La réponse est une enveloppe { "result": { ... } }. Lisez result["status"]deliverable, risky, undeliverable ou unknown — aux côtés d'un score, d'un reason et de sub_flags pour des traits comme les adresses de rôle ou jetables. Consultez la référence de l'API pour la forme exacte des champs ; ici, vous n'avez besoin que du statut pour lever une exception sur une adresse qui ne sera pas délivrée :

def validate_mailbox(email: str) -> None:
    if verify(email)["status"] == "undeliverable":
        raise ValidationError("We couldn't confirm a mailbox at this address.")

Vous pourriez aussi lever une exception sur risky, ou stocker le score et laisser passer l'adresse — c'est une décision de politique. Rejeter undeliverable est le comportement par défaut sûr ; le guide d'inscription serverless explique quel niveau de rigueur adopter au moment de la collecte.

Assembler les couches pour valider une adresse e-mail dans Django#

L'endroit idiomatique pour composer les trois couches sur un formulaire est une méthode clean_<field>. Django n'appelle clean_email qu'après que l'EmailValidator propre au champ a réussi ; la syntaxe est donc déjà gérée. Vous ajoutez les contrôles de domaine et de boîte aux lettres dans l'ordre, chacun levant une exception au plus tôt :

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # layer 1 for free

    def clean_email(self):
        email = self.cleaned_data["email"]  # syntax already passed
        validate_deliverable_domain(email)  # layer 2 — cheap, local
        validate_mailbox(email)             # layer 3 — the API round-trip
        return email

Cet ordre est toute l'astuce : l'EmailValidator du champ court-circuite les déchets avant que votre code ne s'exécute, le contrôle DNS local élimine gratuitement les domaines morts, et l'API n'est sollicitée que pour les adresses qui ont franchi les deux premières couches. Une validation dans le mauvais ordre — ou l'omission des couches bon marché — dépense un crédit sur chaque faute de frappe.

Une chose à trancher d'emblée : que se passe-t-il lorsque l'appel à l'API échoue lui-même. Un délai d'attente réseau ou une exception requests à l'intérieur de clean_email ne doit pas infliger à un vrai client une erreur de validation qu'il ne peut pas corriger. Interceptez l'échec de la requête séparément de la ValidationError, et traitez un service injoignable comme unknown plutôt que undeliverable — laissez passer l'inscription et revérifiez l'adresse plus tard, au lieu de bloquer l'enregistrement à cause d'une panne transitoire. Les couches syntaxe et MX ont déjà été exécutées localement ; vous n'assouplissez donc que la couche qui dépend du réseau.

Parce que les deux validateurs personnalisés lèvent la ValidationError de Django, les deux mêmes fonctions se glissent sans modification dans un sérialiseur Django REST Framework. Le hook validate_<field> de DRF intercepte cette exception et la transforme en un propre 400 :

from rest_framework import serializers

class SignupSerializer(serializers.Serializer):
    email = serializers.EmailField()  # layer 1 for free

    def validate_email(self, value):
        validate_deliverable_domain(value)  # layer 2
        validate_mailbox(value)             # layer 3
        return value

C'est la même structure à trois couches que vous retrouverez dans les versions Node.js et Python de ce guide — c'est le découpage en couches, et non le framework, qui fait fonctionner la validation. Lorsque vous choisissez le service de vérification qui alimente la troisième couche, la comparaison des API aligne les options.

Foire aux questions#

Le EmailValidator de Django suffit-il à valider une adresse e-mail ?#

Pour la syntaxe, oui — validate_email (et chaque EmailField qui l'utilise) est le bon contrôle de premier niveau, et il vaut mieux qu'une expression régulière écrite à la main. Mais il valide la forme, pas la délivrabilité : il ne résout jamais le DNS et ne contacte jamais de serveur de messagerie ; passer ce contrôle signifie donc « ça ressemble à un e-mail », pas « ça sera délivré ». Associez-le à une résolution MX et à une vérification de boîte aux lettres SMTP avant de faire confiance à l'adresse.

Comment écrire un validateur d'e-mail personnalisé dans Django ?#

Un validateur est n'importe quel objet appelable qui prend la valeur et lève une django.core.exceptions.ValidationError en cas d'échec. Définissez une fonction comme validate_deliverable_domain(email) qui lève une exception lorsqu'il y a un problème et renvoie None sinon, puis passez-la dans la liste validators=[...] du champ ou appelez-la depuis la méthode clean_email d'un formulaire. Le même appelable fonctionne dans le hook validate_email d'un sérialiseur DRF, car DRF intercepte lui aussi la ValidationError de Django.

Puis-je vérifier l'existence d'une boîte aux lettres depuis Django sans API ?#

En partie. dnspython confirme que le domaine accepte le courrier, ce qui écarte gratuitement les domaines morts et ne nécessite rien de plus qu'une petite dépendance. Confirmer la boîte aux lettres implique une conversation SMTP que vous pouvez tenter avec smtplib, mais que vous ne devriez pas exécuter depuis votre serveur applicatif — le port 25 est largement bloqué et le résultat dépend de la réputation de votre IP. C'est la couche qu'un service de vérification existe précisément pour gérer.

Dois-je effectuer ces contrôles à l'inscription ou lors du nettoyage d'une liste ?#

Les deux, mais à des profondeurs différentes. Exécutez les couches syntaxe et MX de manière synchrone dans clean_email — elles sont assez rapides pour bloquer la requête et fournir un retour immédiat — et agissez aussi sur le verdict de l'API à cet endroit. Réservez la vérification par lots, plus lourde, au nettoyage de listes et au travail de back-office, où la latence n'a pas d'importance et où vous pouvez traiter les adresses en masse.


Prêt à ajouter la couche SMTP ? Déposez une adresse syntaxiquement parfaite dans le vérificateur d'e-mail gratuit pour voir une chaîne approuvée par un EmailField revenir undeliverable, puis câblez le même verdict dans vos formulaires grâce à la référence de l'API — exemples à copier-coller inclus.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started