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.