Chaque email que vous envoyez transite par un réseau de serveurs soigneusement orchestré, et les enregistrements Mail Exchange (MX) sont les panneaux indicateurs qui guident ce voyage. Comprendre comment valider les enregistrements MX est une compétence fondamentale pour tout développeur construisant des systèmes de vérification d'emails, des formulaires de contact ou des applications qui collectent des adresses email. Ce guide complet explore la validation des enregistrements MX, des concepts de base aux stratégies d'implémentation avancées, vous donnant les connaissances nécessaires pour intégrer une vérification d'email robuste dans vos applications.
Comprendre les enregistrements MX
Les enregistrements Mail Exchange sont des enregistrements DNS qui spécifient quels serveurs de messagerie sont responsables de la réception des emails pour le compte d'un domaine. Lorsque vous envoyez un email à user@example.com, votre serveur de messagerie doit savoir où le délivrer. Les enregistrements MX fournissent cette information en pointant vers les serveurs de messagerie du domaine.
Fonctionnement des enregistrements MX
Lorsqu'un email est envoyé, le serveur de messagerie expéditeur effectue une recherche DNS pour trouver les enregistrements MX du domaine du destinataire. Cette recherche renvoie un ou plusieurs noms d'hôtes de serveurs de messagerie ainsi que des valeurs de priorité indiquant l'ordre de préférence.
Une recherche d'enregistrement MX typique pour gmail.com pourrait renvoyer :
gmail.com. MX 5 gmail-smtp-in.l.google.com. gmail.com. MX 10 alt1.gmail-smtp-in.l.google.com. gmail.com. MX 20 alt2.gmail-smtp-in.l.google.com. gmail.com. MX 30 alt3.gmail-smtp-in.l.google.com. gmail.com. MX 40 alt4.gmail-smtp-in.l.google.com.
Le serveur expéditeur tente d'abord la livraison au serveur de priorité la plus basse (dans ce cas, priorité 5). Si ce serveur est indisponible, il essaie le niveau de priorité suivant, et ainsi de suite. Cette redondance garantit la livraison des emails même lorsque des serveurs individuels sont hors service.
Composants d'un enregistrement MX
Chaque enregistrement MX contient deux informations essentielles :
Priorité (Préférence)
Une valeur numérique indiquant l'ordre dans lequel les serveurs de messagerie doivent être essayés. Les nombres plus bas indiquent une priorité plus élevée. Les serveurs avec la même priorité sont essayés dans un ordre aléatoire, offrant un équilibrage de charge.
Nom d'hôte du serveur de messagerie
Le nom de domaine pleinement qualifié (FQDN) du serveur de messagerie qui gère les emails pour le domaine. Ce nom d'hôte doit être résolu en une adresse IP via un enregistrement A ou AAAA.
Pourquoi les enregistrements MX sont importants pour la vérification des emails
La validation des enregistrements MX sert de point de contrôle critique dans le processus de vérification des emails :
Confirmation de l'existence du domaine
Si un domaine n'a pas d'enregistrements MX, il ne peut généralement pas recevoir d'emails. Certains domaines peuvent avoir un enregistrement A de secours, mais l'absence d'enregistrements MX est souvent un indicateur fort que le domaine n'est pas configuré pour les emails.
Vérification de l'infrastructure
Des enregistrements MX valides qui se résolvent vers des serveurs de messagerie fonctionnels indiquent que le domaine dispose d'une infrastructure email en place. Cela ne garantit pas qu'une adresse spécifique existe, mais confirme que le domaine peut recevoir des emails.
Détection du spam et de la fraude
Les entreprises légitimes maintiennent des enregistrements MX appropriés. Les domaines suspects utilisés pour le spam ou la fraude ont souvent des enregistrements MX mal configurés ou manquants.
Optimisation des performances
Vérifier les enregistrements MX avant de tenter une vérification SMTP évite de perdre du temps à se connecter à des domaines qui ne peuvent pas recevoir d'emails.
Implémentation des recherches d'enregistrements MX
Explorons comment implémenter la validation des enregistrements MX dans différents environnements de programmation.
Implémentation Node.js
Node.js fournit une résolution DNS intégrée via le module dns :
const dns = require('dns').promises;
async function getMxRecords(domain) {
try {
const records = await dns.resolveMx(domain);
// Trier par priorité (la plus basse en premier)
records.sort((a, b) => a.priority - b.priority);
return {
success: true,
domain,
records: records.map(r => ({
exchange: r.exchange,
priority: r.priority
}))
};
} catch (error) {
return {
success: false,
domain,
error: error.code,
message: getMxErrorMessage(error.code)
};
}
}
function getMxErrorMessage(code) {
const messages = {
'ENODATA': 'Aucun enregistrement MX trouvé pour ce domaine',
'ENOTFOUND': 'Le domaine n\'existe pas',
'ETIMEOUT': 'Délai de recherche DNS expiré',
'ESERVFAIL': 'Le serveur DNS n\'a pas répondu'
};
return messages[code] || 'Erreur DNS inconnue';
}
// Utilisation
const result = await getMxRecords('gmail.com');
console.log(result);
Implémentation Python
Le module dns.resolver de Python, issu de la bibliothèque dnspython, fournit des capacités complètes de recherche DNS :
import dns.resolver
import dns.exception
def get_mx_records(domain):
try:
answers = dns.resolver.resolve(domain, 'MX')
records = []
for rdata in answers:
records.append({
'exchange': str(rdata.exchange).rstrip('.'),
'priority': rdata.preference
})
# Trier par priorité
records.sort(key=lambda x: x['priority'])
return {
'success': True,
'domain': domain,
'records': records
}
except dns.resolver.NXDOMAIN:
return {
'success': False,
'domain': domain,
'error': 'NXDOMAIN',
'message': 'Le domaine n\'existe pas'
}
except dns.resolver.NoAnswer:
return {
'success': False,
'domain': domain,
'error': 'NoAnswer',
'message': 'Aucun enregistrement MX trouvé pour ce domaine'
}
except dns.exception.Timeout:
return {
'success': False,
'domain': domain,
'error': 'Timeout',
'message': 'Délai de recherche DNS expiré'
}
# Utilisation
result = get_mx_records('gmail.com')
print(result)
Implémentation Go
Le package net de Go fournit des fonctions de recherche DNS simples :
package main
import (
"fmt"
"net"
"sort"
)
type MxResult struct {
Success bool
Domain string
Records []MxRecord
Error string
}
type MxRecord struct {
Exchange string
Priority uint16
}
func getMxRecords(domain string) MxResult {
records, err := net.LookupMX(domain)
if err != nil {
return MxResult{
Success: false,
Domain: domain,
Error: err.Error(),
}
}
if len(records) == 0 {
return MxResult{
Success: false,
Domain: domain,
Error: "No MX records found",
}
}
// Trier par priorité
sort.Slice(records, func(i, j int) bool {
return records[i].Pref < records[j].Pref
})
result := MxResult{
Success: true,
Domain: domain,
Records: make([]MxRecord, len(records)),
}
for i, r := range records {
result.Records[i] = MxRecord{
Exchange: r.Host,
Priority: r.Pref,
}
}
return result
}
func main() {
result := getMxRecords("gmail.com")
fmt.Printf("%+v\n", result)
}
Techniques avancées de validation MX
Les recherches MX de base confirment l'existence des enregistrements, mais une validation complète des emails nécessite une analyse plus approfondie.
Validation de la connectivité du serveur de messagerie
Les enregistrements MX pointent vers des noms d'hôtes qui doivent être résolus en adresses IP. Vérifiez que les serveurs de messagerie sont effectivement accessibles :
const dns = require('dns').promises;
const net = require('net');
async function validateMxConnectivity(domain) {
// Obtenir les enregistrements MX
const mxResult = await getMxRecords(domain);
if (!mxResult.success) {
return mxResult;
}
// Valider chaque serveur de messagerie
const validatedRecords = [];
for (const record of mxResult.records) {
const validation = await validateMailServer(record.exchange);
validatedRecords.push({
...record,
...validation
});
}
return {
success: true,
domain,
records: validatedRecords,
hasReachableServer: validatedRecords.some(r => r.reachable)
};
}
async function validateMailServer(hostname) {
try {
// Résoudre le nom d'hôte en IP
const addresses = await dns.resolve4(hostname);
if (addresses.length === 0) {
return { reachable: false, error: 'Aucun enregistrement A' };
}
// Tester la connexion au port 25
const connected = await testConnection(addresses[0], 25);
return {
reachable: connected,
ip: addresses[0],
error: connected ? null : 'Connexion refusée'
};
} catch (error) {
return {
reachable: false,
error: error.message
};
}
}
function testConnection(host, port, timeout = 5000) {
return new Promise((resolve) => {
const socket = new net.Socket();
socket.setTimeout(timeout);
socket.on('connect', () => {
socket.destroy();
resolve(true);
});
socket.on('timeout', () => {
socket.destroy();
resolve(false);
});
socket.on('error', () => {
resolve(false);
});
socket.connect(port, host);
});
}
Gestion du repli sur l'enregistrement A
Lorsqu'aucun enregistrement MX n'existe, les normes email (RFC 5321) spécifient que l'enregistrement A du domaine doit être utilisé comme repli. Implémentez ce repli dans votre validation :
async function getMailServers(domain) {
// Essayer d'abord les enregistrements MX
try {
const mxRecords = await dns.resolveMx(domain);
if (mxRecords.length > 0) {
return {
type: 'MX',
servers: mxRecords.sort((a, b) => a.priority - b.priority)
};
}
} catch (error) {
if (error.code !== 'ENODATA') {
throw error;
}
}
// Repli sur l'enregistrement A
try {
const aRecords = await dns.resolve4(domain);
if (aRecords.length > 0) {
return {
type: 'A_FALLBACK',
servers: [{ exchange: domain, priority: 0 }],
warning: 'Utilisation du repli sur enregistrement A - aucun enregistrement MX trouvé'
};
}
} catch (error) {
if (error.code !== 'ENODATA') {
throw error;
}
}
return {
type: 'NONE',
servers: [],
error: 'Aucun serveur de messagerie trouvé pour le domaine'
};
}
Détection des enregistrements MX nuls
La RFC 7505 définit les enregistrements MX "nuls" qui indiquent explicitement qu'un domaine n'accepte pas d'emails. Ces enregistrements ont une seule entrée MX avec une priorité 0 et un nom d'hôte vide (".") :
function hasNullMx(mxRecords) {
if (mxRecords.length === 1) {
const record = mxRecords[0];
if (record.priority === 0 &&
(record.exchange === '.' || record.exchange === '')) {
return true;
}
}
return false;
}
async function validateDomainMx(domain) {
const mxResult = await getMxRecords(domain);
if (!mxResult.success) {
return mxResult;
}
if (hasNullMx(mxResult.records)) {
return {
success: false,
domain,
error: 'NULL_MX',
message: 'Le domaine n\'accepte explicitement pas d\'emails'
};
}
return mxResult;
}
Mise en cache des recherches MX
Les recherches DNS ajoutent de la latence à chaque vérification. Implémentez un système de cache pour améliorer les performances :
class MxCache {
constructor(ttlMs = 3600000) { // TTL par défaut de 1 heure
this.cache = new Map();
this.ttl = ttlMs;
}
get(domain) {
const entry = this.cache.get(domain.toLowerCase());
if (!entry) return null;
if (Date.now() > entry.expiry) {
this.cache.delete(domain.toLowerCase());
return null;
}
return entry.data;
}
set(domain, data) {
this.cache.set(domain.toLowerCase(), {
data,
expiry: Date.now() + this.ttl
});
}
// Respecter les valeurs TTL du DNS lorsqu'elles sont disponibles
setWithTtl(domain, data, ttlSeconds) {
const ttlMs = Math.min(ttlSeconds * 1000, this.ttl);
this.cache.set(domain.toLowerCase(), {
data,
expiry: Date.now() + ttlMs
});
}
}
const mxCache = new MxCache();
async function getMxRecordsCached(domain) {
const cached = mxCache.get(domain);
if (cached) {
return { ...cached, fromCache: true };
}
const result = await getMxRecords(domain);
if (result.success) {
mxCache.set(domain, result);
}
return { ...result, fromCache: false };
}
Modèles courants d'enregistrements MX
Comprendre les configurations MX courantes aide à interpréter les résultats de validation et à identifier les problèmes potentiels.
Principaux fournisseurs d'email
Reconnaître les modèles MX des principaux fournisseurs peut aider à identifier les adresses email gratuites :
const knownProviders = {
'google': [
'gmail-smtp-in.l.google.com',
'googlemail-smtp-in.l.google.com',
'aspmx.l.google.com'
],
'microsoft': [
'outlook-com.olc.protection.outlook.com',
'mail.protection.outlook.com'
],
'yahoo': [
'mta5.am0.yahoodns.net',
'mta6.am0.yahoodns.net',
'mta7.am0.yahoodns.net'
],
'protonmail': [
'mail.protonmail.ch',
'mailsec.protonmail.ch'
]
};
function identifyEmailProvider(mxRecords) {
const exchanges = mxRecords.map(r => r.exchange.toLowerCase());
for (const [provider, patterns] of Object.entries(knownProviders)) {
for (const pattern of patterns) {
if (exchanges.some(ex => ex.includes(pattern.toLowerCase()))) {
return provider;
}
}
}
return 'unknown';
}
Détection de Google Workspace
Les domaines Google Workspace (anciennement G Suite) utilisent les serveurs de messagerie de Google mais ne sont pas des comptes email gratuits :
function isGoogleWorkspace(domain, mxRecords) {
const isGoogleMx = mxRecords.some(r =>
r.exchange.toLowerCase().includes('google') ||
r.exchange.toLowerCase().includes('googlemail')
);
// Vérifier si le domaine n'est pas un domaine grand public connu de Google
const googleConsumerDomains = ['gmail.com', 'googlemail.com'];
const isConsumerDomain = googleConsumerDomains.includes(domain.toLowerCase());
return isGoogleMx && !isConsumerDomain;
}
Détection d'email auto-hébergé
Les domaines qui hébergent leur propre email ont souvent des enregistrements MX pointant vers des sous-domaines :
function isSelfHosted(domain, mxRecords) {
const domainParts = domain.toLowerCase().split('.');
const baseDomain = domainParts.slice(-2).join('.');
return mxRecords.some(r => {
const exchange = r.exchange.toLowerCase();
return exchange.includes(baseDomain) &&
!isKnownProvider(exchange);
});
}
function isKnownProvider(exchange) {
const providers = ['google', 'microsoft', 'yahoo', 'outlook', 'protonmail'];
return providers.some(p => exchange.includes(p));
}
Validation MX dans les pipelines de vérification d'emails
La validation MX est une étape dans un processus complet de vérification d'emails. Comprendre son rôle aide à construire des pipelines de vérification efficaces.
Ordre de vérification
La validation MX se produit généralement tôt dans le pipeline de vérification :
async function verifyEmail(email) {
// 1. Validation de la syntaxe (la plus rapide, sans réseau)
const syntaxResult = validateEmailSyntax(email);
if (!syntaxResult.valid) {
return { valid: false, reason: 'invalid_syntax', details: syntaxResult };
}
const domain = email.split('@')[1];
// 2. Validation des enregistrements MX (recherche DNS rapide)
const mxResult = await validateMxRecords(domain);
if (!mxResult.valid) {
return { valid: false, reason: 'no_mx_records', details: mxResult };
}
// 3. Vérifications supplémentaires (jetable, rôle, etc.)
const domainCheck = await checkDomainReputation(domain);
if (domainCheck.isDisposable) {
return { valid: true, risky: true, reason: 'disposable_domain' };
}
// 4. Vérification SMTP (la plus lente, la plus approfondie)
const smtpResult = await verifySmtp(email, mxResult.records);
return {
valid: smtpResult.exists,
deliverable: smtpResult.deliverable,
mxRecords: mxResult.records,
provider: mxResult.provider
};
}
Recherches MX parallèles
Lors de la vérification de plusieurs emails, parallélisez les recherches MX pour différents domaines :
async function verifyEmailsBatch(emails) {
// Grouper les emails par domaine
const emailsByDomain = {};
for (const email of emails) {
const domain = email.split('@')[1];
if (!emailsByDomain[domain]) {
emailsByDomain[domain] = [];
}
emailsByDomain[domain].push(email);
}
// Rechercher les enregistrements MX pour tous les domaines en parallèle
const domains = Object.keys(emailsByDomain);
const mxResults = await Promise.all(
domains.map(domain => getMxRecordsCached(domain))
);
// Mapper les résultats aux domaines
const mxByDomain = {};
domains.forEach((domain, index) => {
mxByDomain[domain] = mxResults[index];
});
// Traiter les emails avec les données MX
const results = [];
for (const email of emails) {
const domain = email.split('@')[1];
const mx = mxByDomain[domain];
if (!mx.success) {
results.push({ email, valid: false, reason: 'invalid_domain' });
continue;
}
// Continuer avec la vérification SMTP en utilisant les données MX en cache
const smtpResult = await verifySmtp(email, mx.records);
results.push({ email, ...smtpResult });
}
return results;
}
Gestion des erreurs et cas limites
Une validation MX robuste gère gracieusement diverses conditions d'erreur.
Gestion des délais d'expiration DNS
Les problèmes réseau peuvent faire bloquer les recherches DNS. Implémentez une gestion des délais d'expiration :
async function getMxRecordsWithTimeout(domain, timeoutMs = 10000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('DNS_TIMEOUT')), timeoutMs);
});
try {
const result = await Promise.race([
getMxRecords(domain),
timeoutPromise
]);
return result;
} catch (error) {
if (error.message === 'DNS_TIMEOUT') {
return {
success: false,
domain,
error: 'TIMEOUT',
message: 'Délai de recherche DNS expiré',
retryable: true
};
}
throw error;
}
}
Gestion des domaines invalides
Gérez les domaines syntaxiquement invalides avant de tenter des recherches DNS :
function isValidDomain(domain) {
if (!domain || typeof domain !== 'string') {
return false;
}
// Vérifier la longueur
if (domain.length > 253) {
return false;
}
// Vérifier les caractères valides et la structure
const domainRegex = /^(?!-)[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
if (!domainRegex.test(domain)) {
return false;
}
// Vérifier la longueur de chaque label
const labels = domain.split('.');
for (const label of labels) {
if (label.length > 63) {
return false;
}
}
return true;
}
async function validateDomainMxSafe(domain) {
if (!isValidDomain(domain)) {
return {
success: false,
domain,
error: 'INVALID_DOMAIN',
message: 'Le format du domaine est invalide'
};
}
return await getMxRecordsWithTimeout(domain);
}
Gestion des échecs DNS temporaires
Les échecs DNS peuvent être temporaires. Implémentez une logique de nouvelle tentative avec backoff exponentiel :
async function getMxRecordsWithRetry(domain, maxRetries = 3) {
const delays = [1000, 2000, 4000];
for (let attempt = 0; attempt < maxRetries; attempt++) {
const result = await getMxRecordsWithTimeout(domain);
// Ne pas réessayer pour les échecs définitifs
if (result.success ||
result.error === 'NXDOMAIN' ||
result.error === 'ENOTFOUND') {
return result;
}
// Réessayer pour les échecs temporaires
if (result.retryable && attempt < maxRetries - 1) {
await sleep(delays[attempt]);
continue;
}
return result;
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Considérations de sécurité
La validation MX introduit des considérations de sécurité que les développeurs doivent aborder.
Prévention de l'usurpation DNS
Les requêtes DNS standard ne sont pas chiffrées et vulnérables à l'usurpation. Envisagez d'utiliser DNS over HTTPS (DoH) pour les applications sensibles :
const https = require('https');
async function getMxRecordsDoH(domain) {
const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=MX`;
return new Promise((resolve, reject) => {
https.get(url, {
headers: { 'Accept': 'application/dns-json' }
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const response = JSON.parse(data);
if (response.Status !== 0) {
resolve({
success: false,
domain,
error: 'DNS_ERROR',
status: response.Status
});
return;
}
const records = (response.Answer || [])
.filter(a => a.type === 15)
.map(a => {
const [priority, exchange] = a.data.split(' ');
return {
priority: parseInt(priority),
exchange: exchange.replace(/\.$/, '')
};
})
.sort((a, b) => a.priority - b.priority);
resolve({
success: records.length > 0,
domain,
records
});
} catch (error) {
reject(error);
}
});
}).on('error', reject);
});
}
Limitation du débit des requêtes DNS
Prévenez les abus en limitant le débit des requêtes DNS :
class DnsRateLimiter {
constructor(maxQueriesPerSecond = 100) {
this.tokens = maxQueriesPerSecond;
this.maxTokens = maxQueriesPerSecond;
this.lastRefill = Date.now();
}
async acquire() {
this.refillTokens();
if (this.tokens > 0) {
this.tokens--;
return true;
}
// Attendre la disponibilité d'un jeton
await sleep(1000 / this.maxTokens);
return this.acquire();
}
refillTokens() {
const now = Date.now();
const elapsed = now - this.lastRefill;
const tokensToAdd = (elapsed / 1000) * this.maxTokens;
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
const dnsLimiter = new DnsRateLimiter(50);
async function getMxRecordsRateLimited(domain) {
await dnsLimiter.acquire();
return getMxRecords(domain);
}
Utilisation de BillionVerify pour la validation MX
Bien que l'implémentation de la validation MX par vous-même ait une valeur éducative, les services professionnels de vérification d'emails comme BillionVerify gèrent la validation MX dans le cadre d'une vérification d'email complète.
Avantages de l'utilisation d'une API de vérification d'emails
Vérifications complètes
L'API de vérification d'emails de BillionVerify combine la validation MX avec la vérification de syntaxe, la vérification SMTP, la détection d'emails jetables et plus encore en un seul appel d'API. Cela élimine le besoin de maintenir plusieurs systèmes de validation.
Infrastructure optimisée
Les services professionnels maintiennent des résolveurs DNS distribués mondialement, gèrent la mise en cache à grande échelle et optimisent les performances pour des millions de vérifications.
Mises à jour continues
Les configurations des serveurs de messagerie changent constamment. Les services de vérification d'emails mettent continuellement à jour leurs bases de données de fournisseurs connus, de domaines jetables et de modèles de serveurs de messagerie.
Exemple d'intégration API
async function verifyEmailWithBillionVerify(email) {
const response = await fetch('https://api.billionverify.com/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.BILLIONVERIFY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
const result = await response.json();
// Les informations MX sont incluses dans la réponse
console.log('MX valide:', result.mx_found);
console.log('Domaine valide:', result.domain_valid);
console.log('Est livrable:', result.is_deliverable);
return result;
}
Conclusion
La validation des enregistrements MX est un composant fondamental de la vérification des emails qui confirme qu'un domaine peut recevoir des emails avant de tenter des vérifications plus gourmandes en ressources. En implémentant une validation MX appropriée, vous pouvez rapidement filtrer les domaines invalides, optimiser les performances de vérification et créer des applications de gestion d'emails plus fiables.
Points clés à retenir pour la validation des enregistrements MX :
- Toujours vérifier les enregistrements MX avant de tenter une vérification SMTP pour économiser du temps et des ressources
- Gérer le repli sur l'enregistrement A conformément aux normes RFC pour les domaines sans enregistrements MX
- Implémenter la mise en cache pour réduire la surcharge de recherche DNS pour les validations répétées
- Reconnaître les modèles courants pour identifier les fournisseurs d'email et les risques potentiels
- Gérer les erreurs gracieusement avec des délais d'expiration, des nouvelles tentatives et des messages d'erreur appropriés
Que vous construisiez un système personnalisé de vérification d'emails ou que vous intégriez un service comme BillionVerify, comprendre les enregistrements MX vous aide à créer une meilleure gestion des emails dans vos applications. Commencez à implémenter la validation MX dès aujourd'hui et franchissez la première étape vers une vérification complète des emails.