Pour valider une adresse e-mail dans Express, vous mettez en place des
middlewares, pas une seule vérification — et ces middlewares doivent répondre à
trois questions distinctes, dans l'ordre. L'adresse a-t-elle la bonne forme ? Son
domaine peut-il recevoir du courrier ? La boîte aux lettres existe-t-elle
réellement ? Express n'en fournit aucune, mais l'écosystème qui l'entoure réduit
chacune à quelques lignes : express-validator pour la syntaxe, le module natif
dns/promises de Node pour le domaine, et une API de vérification pour la boîte
aux lettres. Ce guide construit les trois sous forme de validateurs sur une seule
route POST et montre exactement où le framework s'arrête et où commence une
vraie vérification. C'est la version « façon Express » du
guide de validation d'e-mail en Node.js :
le raisonnement sur le DNS et le SMTP qui s'y trouve reste intégralement valable.
La réponse courte#
Enchaînez trois validateurs sur le champ email, du moins coûteux au plus
coûteux, et laissez le .bail() d'express-validator court-circuiter dès qu'un
résultat est décisif : utilisez body('email').isEmail() pour la syntaxe, un
validateur personnalisé asynchrone encapsulant resolveMx de dns/promises pour
le domaine, et un second validateur personnalisé asynchrone qui appelle une API de
vérification pour la boîte aux lettres — en renvoyant un 422 lorsque le verdict
est undeliverable. N'essayez pas d'ouvrir vous-même des connexions SMTP depuis
votre processus Express pour sonder les boîtes aux lettres : le port 25 sortant
est bloqué sur la plupart des hébergeurs, et la réponse dépend de la réputation de
l'IP émettrice et du greylisting que vous ne voulez pas réimplémenter. Chaque
couche exclut des adresses de manière moins coûteuse que la précédente ; seule
l'API peut en valider une.
Couche 1 : la syntaxe avec express-validator#
express-validator est le middleware de validation de facto pour Express — un
wrapper fin et natif à Express autour de la bibliothèque validator.js. Sa
vérification isEmail() est le isEmail de validator.js : vous obtenez donc
le même analyseur de syntaxe éprouvé sur lequel s'appuie déjà tout projet Node,
exposé sous forme de middleware chaînable. Déclarez une règle sur le champ et elle
s'exécute avant votre gestionnaire :
import { body } from "express-validator";
const validateEmail = body("email")
.trim()
.isEmail()
.withMessage("Enter a valid email address.")
.bail();
.trim() nettoie les espaces en début et en fin de chaîne, .isEmail() valide la
forme, .withMessage() définit le texte d'erreur, et .bail() arrête la chaîne
pour ce champ dès que la syntaxe échoue — ainsi les couches plus coûteuses
ci-dessous ne s'exécutent jamais sur une chaîne mal formée. Ce .bail() est
l'équivalent Express du fait de s'arrêter à la vérification la moins coûteuse
possible.
Sachez exactement ce que cela vous apporte. isEmail() lit la chaîne — il ne
résout jamais le DNS et n'ouvre jamais de socket. definitely-fake@gmail.com
passe. info@company-that-folded.com passe. typo@gmial.com passe. Ces trois
adresses sont non délivrables, et aucun validateur qui se contente d'inspecter la
chaîne ne vous le dira jamais — la même raison pour laquelle
la validation d'e-mail par regex échoue.
Syntaxe et délivrabilité sont deux questions différentes : l'une est un fait à
propos de la chaîne, l'autre un fait à propos d'Internet.
Couche 2 : le domaine peut-il recevoir du courrier ?#
C'est la première couche qu'Express ne vous offre pas, et elle est peu coûteuse à
ajouter. Un domaine sans enregistrement MX ne peut accepter de courrier pour
personne : une seule recherche DNS élimine donc les domaines morts, les noms
d'entreprise mal orthographiés et les TLD inventés avant de dépenser quoi que ce
soit sur le réseau. Le module natif node:dns/promises de Node résout les
enregistrements MX sans aucune dépendance, et le .custom() d'express-validator
accepte une fonction asynchrone — levez à l'intérieur pour faire échouer le champ,
renvoyez true pour le faire réussir :
import { resolveMx } from "node:dns/promises";
async function hasMailRoute(email) {
const domain = email.slice(email.lastIndexOf("@") + 1);
let records = [];
try {
records = await resolveMx(domain);
} catch {
// ENOTFOUND / ENODATA — the domain doesn't exist or publishes no MX
throw new Error("This domain cannot receive email.");
}
if (records.length === 0) {
throw new Error("This domain cannot receive email.");
}
return true;
}
Comme il s'agit d'une simple fonction asynchrone, elle s'insère directement dans
la chaîne en tant que validateur personnalisé, protégée par un autre .bail()
afin que la couche « boîte aux lettres » ne s'exécute que sur un domaine qui
résout effectivement une route de courrier :
body("email")
.isEmail()
.bail()
.custom(hasMailRoute)
.bail();
await hasMailRoute("jane@gmail.com"); // true
await hasMailRoute("jane@company-that-folded.com"); // throws — no MX
Certains domaines acceptent le courrier sur un enregistrement A sans MX (MX
implicite). Si vous voulez prendre en compte ce cas limite, repliez-vous sur
resolve4 lorsque resolveMx revient vide — mais pour l'écrasante majorité des
adresses réelles, une vérification MX est le bon filtre.
Couche 3 : la boîte aux lettres existe-t-elle réellement ?#
Les couches 1 et 2 ne peuvent qu'exclure une adresse. Un domaine peut publier
des enregistrements MX parfaits et ne toujours avoir aucune boîte aux lettres à
l'adresse que vous détenez — noreply-9f2x@gmail.com a une syntaxe valide sur une
route de courrier 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 RCPT TO, lire la
réponse et se déconnecter avant d'envoyer quoi que ce soit.
En principe, vous pourriez scripter cela depuis Express avec le module net. En
pratique, vous ne devriez pas l'exécuter depuis votre serveur applicatif : la
plupart des hébergeurs 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
destinataires appliquent le greylisting et limitent le débit des expéditeurs
inconnus — de sorte qu'un sondage qui fonctionne dans un test local échoue
discrètement, ou vous fait mettre sur liste de blocage, en production.
Comment fonctionne la vérification d'e-mail
parcourt tout le pipeline, domaines catch-all inclus. C'est la couche qui vaut la
peine d'être déléguée.
Appeler Qualisend depuis un validateur personnalisé asynchrone#
La délégation n'est qu'un validateur personnalisé de plus. Le point de terminaison
de vérification de Qualisend exécute tout le pipeline — syntaxe, DNS et le sondage
SMTP de la boîte aux lettres — depuis une infrastructure à réputation gérée, et
renvoie un verdict. Appelez-le avec le fetch global (intégré à Node 18+), lisez
votre clé depuis une variable d'environnement, et ne levez que lorsque le verdict
est undeliverable :
async function isDeliverable(email) {
const res = await fetch("https://api.qualisend.com/v1/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
// On our own outage or a rate limit, don't block a real signup.
if (!res.ok) return true;
const { result } = await res.json();
if (result.status === "undeliverable") {
throw new Error("We couldn't confirm a mailbox at this address.");
}
return true;
}
Le point de terminaison ci-dessus est un espace réservé — consultez la
référence de l'API pour l'URL de base et la forme exacte de la
requête — mais la réponse revient sous la forme d'une enveloppe
{ "result": { ... } } :
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
}
}
status vaut l'une des valeurs deliverable, risky, undeliverable ou
unknown. Le validateur ci-dessus ne fait échouer durement que undeliverable et
laisse passer risky et unknown, afin que vous puissiez décider en aval quoi en
faire — filtrer sur le score, ou faire une distinction selon une entrée de
sub_flags comme disposable — au lieu de transformer une adresse limite en
erreur de formulaire. Traitez le JSON ci-dessus comme la forme, pas comme le
contrat ; la liste complète des champs se trouve dans la
documentation développeur.
Assembler les couches pour valider une adresse e-mail dans Express#
Composez les trois sur une seule chaîne body("email"), du moins coûteux au plus
coûteux, avec un .bail() entre chacune afin qu'un échec court-circuite avant que
la couche suivante ne s'exécute. Le validateur de l'API ne se déclenche que sur
une adresse qui a déjà franchi la syntaxe et la vérification MX :
import { body } from "express-validator";
const validateEmail = [
body("email")
.trim()
.isEmail()
.withMessage("Enter a valid email address.")
.bail()
.custom(hasMailRoute)
.bail()
.custom(isDeliverable),
];
Branchez ce tableau devant votre gestionnaire de route, puis lisez les erreurs
collectées avec validationResult. Lorsque la chaîne échoue, répondez avec un
422 et le tableau d'erreurs ; sinon, chaque couche a réussi et l'adresse peut
être enregistrée sans risque :
import express from "express";
import { validationResult } from "express-validator";
const app = express();
app.use(express.json());
app.post("/signup", validateEmail, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// Every layer passed — safe to create the account.
const { email } = req.body;
return res.status(201).json({ email });
});
Cet ordre est toute l'astuce : isEmail() court-circuite les déchets avant que
votre code ne s'exécute, la vérification MX locale écarte gratuitement les
domaines morts, et l'API n'est sollicitée que pour les adresses qui ont franchi
les deux. Une validation dans le mauvais ordre — ou le fait de sauter les couches
peu coûteuses — dépense un crédit d'API à chaque faute de frappe. C'est la mise en
couches, et non le framework, qui rend la validation fiable ; elle a la même forme
que la version Node.js, la chaîne de
middlewares d'Express tenant lieu de pipeline.
Une décision à prendre d'emblée : que se passe-t-il lorsque l'appel à l'API
lui-même échoue. Un délai réseau dépassé ou un fetch qui lève à l'intérieur
d'isDeliverable ne devrait pas infliger à un vrai client un 422 qu'il ne peut
pas corriger. Le validateur ci-dessus adoucit déjà ce cas en renvoyant true sur
une réponse non OK — traitant un service injoignable comme unknown plutôt que
comme undeliverable, laissant passer l'inscription et reportant la
revérification à plus tard. Les couches syntaxe et MX se sont déjà exécutées
localement : vous ne relâchez donc que la couche qui dépend du réseau.
Exécutez ce pipeline synchrone rapide au point de collecte et réservez la vérification par lots, plus lourde, au nettoyage de listes. Le guide d'inscription serverless montre le schéma en temps réel de bout en bout, et lorsque vous choisissez le service qui alimente la troisième couche, la comparaison des API aligne les options.
Foire aux questions#
Le isEmail d'express-validator suffit-il à valider une adresse e-mail ?#
Pour la syntaxe, oui — body("email").isEmail() encapsule le isEmail de
validator.js et constitue la bonne vérification de premier niveau, bien meilleure
qu'une regex codée à 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, donc une
réussite signifie « ça ressemble à un e-mail », pas « ça sera bien délivré ».
Associez-le à une recherche MX et à une vérification de la boîte aux lettres avant
de faire confiance à l'adresse.
Comment écrire un validateur personnalisé asynchrone dans Express ?#
Passez une fonction asynchrone au .custom() d'express-validator. La fonction
reçoit la valeur du champ ; renvoyez true (ou résolvez) pour réussir, et throw
une Error (ou rejetez) pour échouer — le message levé devient l'erreur de
validation. C'est ainsi que les fonctions hasMailRoute et isDeliverable
ci-dessus se branchent sur la même chaîne body("email"). Placez un .bail()
avant chacune pour qu'une couche échouée arrête la chaîne au lieu d'exécuter la
suivante.
Puis-je vérifier l'existence d'une boîte aux lettres dans Express sans API ?#
En partie. node:dns/promises confirme que le domaine accepte le courrier, ce qui
écarte gratuitement les domaines morts et ne nécessite aucune dépendance.
Confirmer la boîte aux lettres implique une conversation SMTP que vous pouvez
tenter avec le module net, 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 précisément la couche qu'un service de vérification
est fait pour gérer ; ce qu'est la vérification d'e-mail
en couvre les termes.
Faut-il renvoyer un 422 ou laisser passer l'inscription en cas de résultat inconnu ?#
Rejetez undeliverable avec un 422 — c'est une mauvaise adresse confirmée que
l'utilisateur peut corriger. Mais ne provoquez pas d'échec à cause d'une panne de
votre propre service de vérification : traitez une réponse non OK, un délai
dépassé ou un statut unknown comme « laisser passer et revérifier plus tard »,
afin qu'un problème réseau passager ne bloque jamais une inscription légitime. Le
validateur isDeliverable fait exactement cela en renvoyant true lorsque la
réponse n'est pas OK.
Prêt à ajouter la couche « boîte aux lettres » ? Déposez une adresse
syntaxiquement parfaite dans le vérificateur d'e-mail gratuit
pour voir une chaîne approuvée par isEmail revenir undeliverable, puis branchez
le même verdict dans votre middleware Express grâce aux exemples à copier-coller de
la référence de l'API.