Elke e-mail die je verstuurt, reist door een zorgvuldig georkestreerd netwerk van servers, en Mail Exchange (MX) records zijn de wegwijzers die deze reis begeleiden. Het begrijpen van hoe je MX records valideert, is een fundamentele vaardigheid voor elke ontwikkelaar die e-mailverificatiesystemen, contactformulieren of applicaties bouwt die e-mailadressen verzamelen. Deze uitgebreide gids verkent MX record validatie van basisconcepten tot geavanceerde implementatiestrategieën, waardoor je de kennis krijgt om robuuste e-mailverificatie in je applicaties te bouwen.
MX Records Begrijpen
Mail Exchange records zijn DNS-records die specificeren welke mailservers verantwoordelijk zijn voor het accepteren van e-mail namens een domein. Wanneer je een e-mail stuurt naar user@example.com, moet je mailserver weten waar deze te bezorgen. MX records leveren deze informatie door te verwijzen naar de mailservers van het domein.
Hoe MX Records Werken
Wanneer een e-mail wordt verzonden, voert de verzendende mailserver een DNS lookup uit om de MX records voor het domein van de ontvanger te vinden. Deze lookup retourneert een of meer mailserver hostnamen samen met prioriteitswaarden die de voorkeursvolgorde aangeven.
Een typische MX record lookup voor gmail.com zou kunnen teruggeven:
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.
De verzendende server probeert eerst bezorging naar de server met de laagste prioriteit (in dit geval prioriteit 5). Als die server niet beschikbaar is, probeert deze het volgende prioriteitsniveau, enzovoort. Deze redundantie zorgt voor e-mailbezorging zelfs wanneer individuele servers offline zijn.
MX Record Componenten
Elk MX record bevat twee essentiële informatiestukken:
Prioriteit (Voorkeur)
Een numerieke waarde die de volgorde aangeeft waarin mailservers geprobeerd moeten worden. Lagere nummers geven hogere prioriteit aan. Servers met dezelfde prioriteit worden in willekeurige volgorde geprobeerd, wat load balancing biedt.
Mailserver Hostnaam
De Fully Qualified Domain Name (FQDN) van de mailserver die e-mail afhandelt voor het domein. Deze hostnaam moet oplossen naar een IP-adres via een A of AAAA record.
Waarom MX Records Belangrijk Zijn voor E-mailverificatie
MX record validatie dient als een kritisch controlepunt in het e-mailverificatieproces:
Bevestiging van Domeinbestaan
Als een domein geen MX records heeft, kan het doorgaans geen e-mail ontvangen. Sommige domeinen hebben mogelijk een A record fallback, maar de afwezigheid van MX records is vaak een sterke indicator dat het domein niet is geconfigureerd voor e-mail.
Infrastructuurverificatie
Geldige MX records die oplossen naar werkende mailservers geven aan dat het domein e-mailinfrastructuur heeft. Dit garandeert niet dat een specifiek adres bestaat, maar bevestigt wel dat het domein e-mail kan ontvangen.
Spam en Fraudedetectie
Legitieme bedrijven onderhouden correcte MX records. Verdachte domeinen die worden gebruikt voor spam of fraude hebben vaak verkeerd geconfigureerde of ontbrekende MX records.
Prestatieoptimalisatie
Het controleren van MX records voordat je SMTP-verificatie probeert, voorkomt verspilde tijd bij het verbinden met domeinen die geen e-mail kunnen ontvangen.
MX Record Lookups Implementeren
Laten we verkennen hoe je MX record validatie implementeert in verschillende programmeeromgevingen.
Node.js Implementatie
Node.js biedt ingebouwde DNS-resolutie via de dns module:
const dns = require('dns').promises;
async function getMxRecords(domain) {
try {
const records = await dns.resolveMx(domain);
// Sort by priority (lowest first)
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': 'No MX records found for this domain',
'ENOTFOUND': 'Domain does not exist',
'ETIMEOUT': 'DNS lookup timed out',
'ESERVFAIL': 'DNS server failed to respond'
};
return messages[code] || 'Unknown DNS error';
}
// Usage
const result = await getMxRecords('gmail.com');
console.log(result);
Python Implementatie
Python's dns.resolver module uit de dnspython bibliotheek biedt uitgebreide DNS lookup mogelijkheden:
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
})
# Sort by priority
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': 'Domain does not exist'
}
except dns.resolver.NoAnswer:
return {
'success': False,
'domain': domain,
'error': 'NoAnswer',
'message': 'No MX records found for this domain'
}
except dns.exception.Timeout:
return {
'success': False,
'domain': domain,
'error': 'Timeout',
'message': 'DNS lookup timed out'
}
# Usage
result = get_mx_records('gmail.com')
print(result)
Go Implementatie
Go's net package biedt eenvoudige DNS lookup functies:
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",
}
}
// Sort by priority
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)
}
Geavanceerde MX Validatietechnieken
Basis MX lookups bevestigen dat records bestaan, maar uitgebreide e-mailvalidatie vereist diepere analyse.
Mailserver Connectiviteit Valideren
MX records verwijzen naar hostnamen die moeten oplossen naar IP-adressen. Verifieer dat de mailservers daadwerkelijk bereikbaar zijn:
const dns = require('dns').promises;
const net = require('net');
async function validateMxConnectivity(domain) {
// Get MX records
const mxResult = await getMxRecords(domain);
if (!mxResult.success) {
return mxResult;
}
// Validate each mail server
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 {
// Resolve hostname to IP
const addresses = await dns.resolve4(hostname);
if (addresses.length === 0) {
return { reachable: false, error: 'No A record' };
}
// Test connection to port 25
const connected = await testConnection(addresses[0], 25);
return {
reachable: connected,
ip: addresses[0],
error: connected ? null : 'Connection refused'
};
} 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);
});
}
A Record Fallback Afhandelen
Wanneer er geen MX records bestaan, specificeren e-mailstandaarden (RFC 5321) dat het A record van het domein als fallback gebruikt moet worden. Implementeer deze fallback in je validatie:
async function getMailServers(domain) {
// Try MX records first
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;
}
}
// Fallback to A record
try {
const aRecords = await dns.resolve4(domain);
if (aRecords.length > 0) {
return {
type: 'A_FALLBACK',
servers: [{ exchange: domain, priority: 0 }],
warning: 'Using A record fallback - no MX records found'
};
}
} catch (error) {
if (error.code !== 'ENODATA') {
throw error;
}
}
return {
type: 'NONE',
servers: [],
error: 'No mail servers found for domain'
};
}
Null MX Records Detecteren
RFC 7505 definieert "null MX" records die expliciet aangeven dat een domein geen e-mail accepteert. Deze records hebben een enkel MX entry met prioriteit 0 en een lege hostnaam ("."):
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: 'Domain explicitly does not accept email'
};
}
return mxResult;
}
MX Lookups Cachen
DNS lookups voegen latency toe aan elke verificatie. Implementeer caching om de prestaties te verbeteren:
class MxCache {
constructor(ttlMs = 3600000) { // 1 hour default TTL
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
});
}
// Respect DNS TTL values when available
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 };
}
Veelvoorkomende MX Record Patronen
Het begrijpen van veelvoorkomende MX configuraties helpt je validatieresultaten te interpreteren en potentiële problemen te identificeren.
Grote E-mailproviders
Het herkennen van MX patronen voor grote providers kan helpen gratis e-mailadressen te identificeren:
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';
}
Google Workspace Detectie
Google Workspace (voorheen G Suite) domeinen gebruiken Google's mailservers maar zijn geen gratis e-mailaccounts:
function isGoogleWorkspace(domain, mxRecords) {
const isGoogleMx = mxRecords.some(r =>
r.exchange.toLowerCase().includes('google') ||
r.exchange.toLowerCase().includes('googlemail')
);
// Check if domain is not a known Google consumer domain
const googleConsumerDomains = ['gmail.com', 'googlemail.com'];
const isConsumerDomain = googleConsumerDomains.includes(domain.toLowerCase());
return isGoogleMx && !isConsumerDomain;
}
Zelf-gehoste E-mail Detectie
Domeinen die hun eigen e-mail hosten, hebben vaak MX records die verwijzen naar subdomeinen:
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));
}
MX Validatie in E-mailverificatie Pipelines
MX validatie is een stap in een uitgebreid e-mailverificatieproces. Het begrijpen van zijn rol helpt effectieve verificatiepipelines te bouwen.
Verificatievolgorde
MX validatie vindt doorgaans vroeg in de verificatiepipeline plaats:
async function verifyEmail(email) {
// 1. Syntax validation (fastest, no network)
const syntaxResult = validateEmailSyntax(email);
if (!syntaxResult.valid) {
return { valid: false, reason: 'invalid_syntax', details: syntaxResult };
}
const domain = email.split('@')[1];
// 2. MX record validation (fast DNS lookup)
const mxResult = await validateMxRecords(domain);
if (!mxResult.valid) {
return { valid: false, reason: 'no_mx_records', details: mxResult };
}
// 3. Additional checks (disposable, role-based, etc.)
const domainCheck = await checkDomainReputation(domain);
if (domainCheck.isDisposable) {
return { valid: true, risky: true, reason: 'disposable_domain' };
}
// 4. SMTP verification (slowest, most thorough)
const smtpResult = await verifySmtp(email, mxResult.records);
return {
valid: smtpResult.exists,
deliverable: smtpResult.deliverable,
mxRecords: mxResult.records,
provider: mxResult.provider
};
}
Parallelle MX Lookups
Bij het verifiëren van meerdere e-mails, parallelliseer MX lookups voor verschillende domeinen:
async function verifyEmailsBatch(emails) {
// Group emails by domain
const emailsByDomain = {};
for (const email of emails) {
const domain = email.split('@')[1];
if (!emailsByDomain[domain]) {
emailsByDomain[domain] = [];
}
emailsByDomain[domain].push(email);
}
// Lookup MX records for all domains in parallel
const domains = Object.keys(emailsByDomain);
const mxResults = await Promise.all(
domains.map(domain => getMxRecordsCached(domain))
);
// Map results back to domains
const mxByDomain = {};
domains.forEach((domain, index) => {
mxByDomain[domain] = mxResults[index];
});
// Process emails with MX data
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;
}
// Continue with SMTP verification using cached MX data
const smtpResult = await verifySmtp(email, mx.records);
results.push({ email, ...smtpResult });
}
return results;
}
Foutafhandeling en Randgevallen
Robuuste MX validatie handelt verschillende foutcondities netjes af.
DNS Timeout Afhandeling
Netwerkproblemen kunnen ervoor zorgen dat DNS lookups vastlopen. Implementeer timeout afhandeling:
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: 'DNS lookup timed out',
retryable: true
};
}
throw error;
}
}
Ongeldige Domein Afhandeling
Behandel domeinen die syntactisch ongeldig zijn voordat je DNS lookups probeert:
function isValidDomain(domain) {
if (!domain || typeof domain !== 'string') {
return false;
}
// Check length
if (domain.length > 253) {
return false;
}
// Check for valid characters and structure
const domainRegex = /^(?!-)[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
if (!domainRegex.test(domain)) {
return false;
}
// Check each label length
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: 'Domain format is invalid'
};
}
return await getMxRecordsWithTimeout(domain);
}
Tijdelijke DNS Fouten Afhandelen
DNS fouten kunnen tijdelijk zijn. Implementeer retry logica met exponential backoff:
async function getMxRecordsWithRetry(domain, maxRetries = 3) {
const delays = [1000, 2000, 4000];
for (let attempt = 0; attempt < maxRetries; attempt++) {
const result = await getMxRecordsWithTimeout(domain);
// Don't retry for definitive failures
if (result.success ||
result.error === 'NXDOMAIN' ||
result.error === 'ENOTFOUND') {
return result;
}
// Retry for temporary failures
if (result.retryable && attempt < maxRetries - 1) {
await sleep(delays[attempt]);
continue;
}
return result;
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Beveiligingsoverwegingen
MX validatie introduceert beveiligingsoverwegingen die ontwikkelaars moeten aanpakken.
DNS Spoofing Preventie
Standaard DNS queries zijn onversleuteld en kwetsbaar voor spoofing. Overweeg DNS over HTTPS (DoH) te gebruiken voor gevoelige applicaties:
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);
});
}
Rate Limiting DNS Queries
Voorkom misbruik door de snelheid van DNS queries te beperken:
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;
}
// Wait for token availability
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);
}
BillionVerify Gebruiken voor MX Validatie
Hoewel het zelf implementeren van MX validatie educatieve waarde biedt, behandelen professionele e-mailverificatieservices zoals BillionVerify MX validatie als onderdeel van uitgebreide e-mailverificatie.
Voordelen van een E-mailverificatie API Gebruiken
Uitgebreide Controles
BillionVerify's e-mailverificatie API combineert MX validatie met syntaxcontrole, SMTP-verificatie, detectie van wegwerp-e-mail en meer in een enkele API call. Dit elimineert de noodzaak om meerdere validatiesystemen te onderhouden.
Geoptimaliseerde Infrastructuur
Professionele services onderhouden wereldwijd verspreide DNS resolvers, handelen caching op schaal af en optimaliseren voor prestaties over miljoenen verificaties.
Continue Updates
Mailserver configuraties veranderen constant. E-mailverificatieservices updaten voortdurend hun databases van bekende providers, wegwerpdomeinen en mailserver patronen.
API Integratie Voorbeeld
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();
// MX information is included in the response
console.log('MX Valid:', result.mx_found);
console.log('Domain Valid:', result.domain_valid);
console.log('Is Deliverable:', result.is_deliverable);
return result;
}
Conclusie
MX record validatie is een fundamenteel onderdeel van e-mailverificatie dat bevestigt dat een domein e-mail kan ontvangen voordat meer resource-intensieve controles worden geprobeerd. Door correcte MX validatie te implementeren, kun je snel ongeldige domeinen filteren, verificatieprestaties optimaliseren en betrouwbaardere e-mailverwerkende applicaties bouwen.
Belangrijkste lessen voor MX record validatie:
- Controleer altijd MX records voordat je SMTP-verificatie probeert om tijd en resources te besparen
- Behandel de A record fallback volgens RFC-standaarden voor domeinen zonder MX records
- Implementeer caching om DNS lookup overhead te verminderen voor herhaalde validaties
- Herken veelvoorkomende patronen om e-mailproviders en potentiële risico's te identificeren
- Behandel fouten netjes met timeouts, retries en correcte foutmeldingen
Of je nu een aangepast e-mailverificatiesysteem bouwt of integreert met een service zoals BillionVerify, het begrijpen van MX records helpt je betere e-mailafhandeling in je applicaties te bouwen. Begin vandaag nog met het implementeren van MX validatie en zet de eerste stap naar uitgebreide e-mailverificatie.