Vérification d'e-mails en Node.js
Dans ce tutoriel, vous allez ajouter la vérification d'e-mails en temps réel à une application Node.js sous Express. À la fin, les adresses jetables, les boîtes inexistantes et les adresses mal tapées seront rejetées au niveau de l'endpoint d'inscription, avant la création du moindre enregistrement utilisateur.
Ce que vous allez construire
Un middleware Express réutilisable qui :
- Vérifie une adresse via l'API Mailbeam
- Renvoie un 422 avec un code d'erreur exploitable par la machine en cas d'échec
- Échoue en mode permissif sur les erreurs d'API (pour qu'une panne de Mailbeam ne casse pas vos inscriptions)
- Met éventuellement les résultats en cache pour éviter les vérifications en double
Prérequis
- Node.js 18 ou plus récent
- Un projet npm, pnpm ou yarn
- Un compte Mailbeam et une clé d'API (inscription gratuite)
Étape 1 — Ajouter un petit client
Il n'y a pas de SDK à installer. Mailbeam se résume à un endpoint HTTP : une douzaine de lignes dans votre propre code suffisent, et il n'y a rien à maintenir à jour. Des SDK officiels sont à la feuille de route ; voici ce qu'il faut faire aujourd'hui.
// lib/mailbeam.js
const ENDPOINT = "https://api.mailbeam.dev/v1/verify";
export async function verifyEmail(email, { timeoutMs = 3000 } = {}) {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAILBEAM_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) {
const { error, message } = await response.json();
throw Object.assign(new Error(message), { code: error });
}
return response.json();
}Étape 2 — Déclarer votre clé d'API
Ajoutez votre clé à .env ou .env.local :
MAILBEAM_KEY=mb_live_xxxxxxxxxxxxxxxxxxxxÉtape 3 — Créer le middleware de vérification
Créez middleware/verifyEmail.js :
import { verifyEmail } from "../lib/mailbeam.js";
/**
* Middleware Express qui vérifie l'adresse présente dans req.body.email.
* Appelle next() en cas de succès, renvoie un 422 en cas d'échec.
* Échec permissif sur les erreurs de l'API Mailbeam — ne bloquez pas
* vos utilisateurs si l'API est indisponible.
*/
export async function verifyEmail(req, res, next) {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "email is required." });
}
try {
const { valid, score, reason } = await verifyEmail(email);
if (!valid || score < 60) {
return res.status(422).json({
error: "Merci de fournir une adresse e-mail valide.",
code: reason ?? "invalid_email",
});
}
// Attache le résultat à la requête, pour les handlers en aval
req.emailVerification = { valid, score, reason };
next();
} catch (err) {
// Journalise sans bloquer — une panne de Mailbeam ne doit pas casser l'inscription
console.error("[Mailbeam] erreur de vérification :", err.message);
next();
}
}Étape 4 — L'ajouter à votre route d'inscription
// routes/auth.js
import express from "express";
import { verifyEmail } from "../middleware/verifyEmail.js";
const router = express.Router();
router.post("/signup", verifyEmail, async (req, res) => {
const { email, password } = req.body;
try {
const user = await db.createUser({ email, password });
res.status(201).json({ user });
} catch (err) {
res.status(500).json({ error: "La création du compte a échoué." });
}
});
export default router;Et la configuration de votre application Express :
// app.js
import express from "express";
import authRoutes from "./routes/auth.js";
const app = express();
app.use(express.json());
app.use("/api/auth", authRoutes);
app.listen(3000, () => {
console.log("Serveur démarré sur http://localhost:3000");
});Tester l'intégration
Démarrez votre serveur, puis testez avec cURL :
# Adresse valide — doit renvoyer 201
curl -X POST http://localhost:3000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"email": "valid@example.com", "password": "secretpass"}'
# Adresse jetable — doit renvoyer 422
curl -X POST http://localhost:3000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"email": "throwaway@mailinator.com", "password": "secretpass"}'Utilisez les domaines de test de Mailbeam dans votre chaîne d'intégration continue, pour ne pas entamer votre quota :
# Renvoie toujours valide (score 99)
curl ... -d '{"email": "user@valid.mailbeam-test.dev", ...}'
# Renvoie toujours invalide
curl ... -d '{"email": "user@invalid.mailbeam-test.dev", ...}'Ajouter une couche de cache (facultatif)
Pour des inscriptions à fort trafic, mettez les résultats en cache afin d'éviter les appels redondants :
import { LRUCache } from "lru-cache";
const cache = new LRUCache({
max: 500,
ttl: 1000 * 60 * 60, // 1 heure
});
export async function verifyEmail(req, res, next) {
const { email } = req.body;
if (!email) return res.status(400).json({ error: "email is required." });
const normalised = email.toLowerCase().trim();
const cached = cache.get(normalised);
if (cached) {
if (!cached.valid || cached.score < 60) {
return res.status(422).json({
error: "Merci de fournir une adresse e-mail valide.",
code: cached.reason ?? "invalid_email",
});
}
return next();
}
try {
const result = await verifyEmail(normalised);
cache.set(normalised, result);
if (!result.valid || result.score < 60) {
return res.status(422).json({
error: "Merci de fournir une adresse e-mail valide.",
code: result.reason ?? "invalid_email",
});
}
next();
} catch (err) {
console.error("[Mailbeam] erreur de vérification :", err.message);
next();
}
}Bonnes pratiques
| Pratique | Pourquoi |
|---|---|
| Échec permissif sur les erreurs d'API | Une panne de Mailbeam ne doit pas empêcher les inscriptions |
| Mettre en cache (TTL d'une heure) | Évite de vérifier deux fois la même adresse |
| Passer en minuscules et élaguer avant de vérifier | Évite les ratés de cache pour des différences insignifiantes |
| Utiliser les domaines de test en CI | Ne consomme pas le quota de production dans les tests automatisés |
| Journaliser les erreurs, pas les adresses | Évite de journaliser des données personnelles en production |
Liste de contrôle avant la mise en production
-
MAILBEAM_KEYdéclarée comme secret de plateforme (pas dans le code source) - Journalisation des erreurs en place (sans écrire l'adresse elle-même)
- Couche de cache ajoutée sur les routes à fort trafic
- Domaines de test utilisés dans la chaîne d'intégration continue
- Seuil de score arrêté et documenté (60 par défaut)
- Le front-end affiche un message clair quand un 422 revient