Cada email que você envia viaja através de uma rede cuidadosamente orquestrada de servidores, e os registros Mail Exchange (MX) são as sinalizações que guiam essa jornada. Compreender como validar registros MX é uma habilidade fundamental para qualquer desenvolvedor que esteja construindo sistemas de verificação de email, formulários de contato ou aplicações que coletam endereços de email. Este guia abrangente explora a validação de registros MX, desde conceitos básicos até estratégias avançadas de implementação, fornecendo o conhecimento necessário para criar verificação de email robusta em suas aplicações.
Compreendendo os Registros MX
Os registros Mail Exchange são registros DNS que especificam quais servidores de email são responsáveis por aceitar emails em nome de um domínio. Quando você envia um email para user@example.com, seu servidor de email precisa saber onde entregá-lo. Os registros MX fornecem essa informação apontando para os servidores de email do domínio.
Como os Registros MX Funcionam
Quando um email é enviado, o servidor de email remetente executa uma pesquisa DNS para encontrar os registros MX do domínio do destinatário. Esta pesquisa retorna um ou mais nomes de host de servidores de email, juntamente com valores de prioridade que indicam a ordem de preferência.
Uma pesquisa típica de registro MX para gmail.com pode retornar:
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.
O servidor remetente tenta a entrega primeiro para o servidor de menor prioridade (neste caso, prioridade 5). Se esse servidor estiver indisponível, ele tenta o próximo nível de prioridade, e assim por diante. Essa redundância garante a entrega de email mesmo quando servidores individuais estão offline.
Componentes do Registro MX
Cada registro MX contém duas informações essenciais:
Prioridade (Preferência)
Um valor numérico que indica a ordem em que os servidores de email devem ser testados. Números mais baixos indicam maior prioridade. Servidores com a mesma prioridade são testados em ordem aleatória, proporcionando balanceamento de carga.
Nome do Host do Servidor de Email
O nome de domínio totalmente qualificado (FQDN) do servidor de email que processa emails para o domínio. Este nome de host deve ser resolvido para um endereço IP através de um registro A ou AAAA.
Por Que os Registros MX São Importantes para a Verificação de Email
A validação de registros MX serve como um ponto de verificação crítico no processo de verificação de email:
Confirmação da Existência do Domínio
Se um domínio não tiver registros MX, normalmente não pode receber email. Alguns domínios podem ter um fallback de registro A, mas a ausência de registros MX é frequentemente um forte indicador de que o domínio não está configurado para email.
Verificação de Infraestrutura
Registros MX válidos que resolvem para servidores de email funcionais indicam que o domínio possui infraestrutura de email implementada. Isso não garante que um endereço específico exista, mas confirma que o domínio pode receber email.
Detecção de Spam e Fraude
Empresas legítimas mantêm registros MX adequados. Domínios suspeitos usados para spam ou fraude frequentemente possuem registros MX mal configurados ou ausentes.
Otimização de Desempenho
Verificar registros MX antes de tentar verificação SMTP evita perda de tempo conectando-se a domínios que não podem receber email.
Implementando Pesquisas de Registros MX
Vamos explorar como implementar a validação de registros MX em diferentes ambientes de programação.
Implementação em Node.js
Node.js fornece resolução DNS integrada através do módulo dns:
const dns = require('dns').promises;
async function getMxRecords(domain) {
try {
const records = await dns.resolveMx(domain);
// Ordenar por prioridade (menor primeiro)
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';
}
// Uso
const result = await getMxRecords('gmail.com');
console.log(result);
Implementação em Python
O módulo dns.resolver do Python da biblioteca dnspython fornece capacidades abrangentes de pesquisa 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
})
# Ordenar por prioridade
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'
}
# Uso
result = get_mx_records('gmail.com')
print(result)
Implementação em Go
O pacote net do Go fornece funções diretas de pesquisa DNS:
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",
}
}
// Ordenar por prioridade
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)
}
Técnicas Avançadas de Validação MX
Pesquisas MX básicas confirmam que os registros existem, mas a validação abrangente de email requer análise mais profunda.
Validando Conectividade do Servidor de Email
Registros MX apontam para nomes de host que devem ser resolvidos para endereços IP. Verifique se os servidores de email estão realmente acessíveis:
const dns = require('dns').promises;
const net = require('net');
async function validateMxConnectivity(domain) {
// Obter registros MX
const mxResult = await getMxRecords(domain);
if (!mxResult.success) {
return mxResult;
}
// Validar cada servidor de email
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 {
// Resolver nome do host para IP
const addresses = await dns.resolve4(hostname);
if (addresses.length === 0) {
return { reachable: false, error: 'No A record' };
}
// Testar conexão na porta 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);
});
}
Gerenciando Fallback de Registro A
Quando não existem registros MX, os padrões de email (RFC 5321) especificam que o registro A do domínio deve ser usado como fallback. Implemente esse fallback em sua validação:
async function getMailServers(domain) {
// Tentar registros MX primeiro
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 para registro A
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'
};
}
Detectando Registros MX Nulos
A RFC 7505 define registros "null MX" que indicam explicitamente que um domínio não aceita email. Esses registros possuem uma única entrada MX com prioridade 0 e um nome de host vazio ("."):
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;
}
Cacheando Pesquisas MX
Pesquisas DNS adicionam latência a cada verificação. Implemente cache para melhorar o desempenho:
class MxCache {
constructor(ttlMs = 3600000) { // TTL padrão de 1 hora
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
});
}
// Respeitar valores TTL do DNS quando disponíveis
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 };
}
Padrões Comuns de Registros MX
Compreender configurações MX comuns ajuda a interpretar resultados de validação e identificar possíveis problemas.
Principais Provedores de Email
Reconhecer padrões MX de provedores principais pode ajudar a identificar endereços de email gratuitos:
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';
}
Detecção do Google Workspace
Domínios do Google Workspace (antigo G Suite) usam servidores de email do Google, mas não são contas de email gratuitas:
function isGoogleWorkspace(domain, mxRecords) {
const isGoogleMx = mxRecords.some(r =>
r.exchange.toLowerCase().includes('google') ||
r.exchange.toLowerCase().includes('googlemail')
);
// Verificar se o domínio não é um domínio consumidor conhecido do Google
const googleConsumerDomains = ['gmail.com', 'googlemail.com'];
const isConsumerDomain = googleConsumerDomains.includes(domain.toLowerCase());
return isGoogleMx && !isConsumerDomain;
}
Detecção de Email Auto-Hospedado
Domínios que hospedam seu próprio email frequentemente possuem registros MX apontando para subdomínios:
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));
}
Validação MX em Pipelines de Verificação de Email
A validação MX é uma etapa em um processo abrangente de verificação de email. Compreender seu papel ajuda a construir pipelines de verificação eficazes.
Ordem de Verificação
A validação MX normalmente ocorre no início do pipeline de verificação:
async function verifyEmail(email) {
// 1. Validação de sintaxe (mais rápida, sem rede)
const syntaxResult = validateEmailSyntax(email);
if (!syntaxResult.valid) {
return { valid: false, reason: 'invalid_syntax', details: syntaxResult };
}
const domain = email.split('@')[1];
// 2. Validação de registro MX (pesquisa DNS rápida)
const mxResult = await validateMxRecords(domain);
if (!mxResult.valid) {
return { valid: false, reason: 'no_mx_records', details: mxResult };
}
// 3. Verificações adicionais (descartável, baseado em função, etc.)
const domainCheck = await checkDomainReputation(domain);
if (domainCheck.isDisposable) {
return { valid: true, risky: true, reason: 'disposable_domain' };
}
// 4. Verificação SMTP (mais lenta, mais completa)
const smtpResult = await verifySmtp(email, mxResult.records);
return {
valid: smtpResult.exists,
deliverable: smtpResult.deliverable,
mxRecords: mxResult.records,
provider: mxResult.provider
};
}
Pesquisas MX Paralelas
Ao verificar vários emails, paralelizar pesquisas MX para diferentes domínios:
async function verifyEmailsBatch(emails) {
// Agrupar emails por domínio
const emailsByDomain = {};
for (const email of emails) {
const domain = email.split('@')[1];
if (!emailsByDomain[domain]) {
emailsByDomain[domain] = [];
}
emailsByDomain[domain].push(email);
}
// Pesquisar registros MX para todos os domínios em paralelo
const domains = Object.keys(emailsByDomain);
const mxResults = await Promise.all(
domains.map(domain => getMxRecordsCached(domain))
);
// Mapear resultados de volta aos domínios
const mxByDomain = {};
domains.forEach((domain, index) => {
mxByDomain[domain] = mxResults[index];
});
// Processar emails com dados 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;
}
// Continuar com verificação SMTP usando dados MX em cache
const smtpResult = await verifySmtp(email, mx.records);
results.push({ email, ...smtpResult });
}
return results;
}
Tratamento de Erros e Casos Extremos
A validação MX robusta lida graciosamente com várias condições de erro.
Gerenciamento de Timeout de DNS
Problemas de rede podem fazer com que pesquisas DNS travem. Implemente tratamento de timeout:
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;
}
}
Gerenciamento de Domínio Inválido
Trate domínios sintaticamente inválidos antes de tentar pesquisas DNS:
function isValidDomain(domain) {
if (!domain || typeof domain !== 'string') {
return false;
}
// Verificar comprimento
if (domain.length > 253) {
return false;
}
// Verificar caracteres válidos e estrutura
const domainRegex = /^(?!-)[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
if (!domainRegex.test(domain)) {
return false;
}
// Verificar comprimento de cada rótulo
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);
}
Gerenciando Falhas Temporárias de DNS
Falhas de DNS podem ser temporárias. Implemente lógica de retry com backoff exponencial:
async function getMxRecordsWithRetry(domain, maxRetries = 3) {
const delays = [1000, 2000, 4000];
for (let attempt = 0; attempt < maxRetries; attempt++) {
const result = await getMxRecordsWithTimeout(domain);
// Não tentar novamente para falhas definitivas
if (result.success ||
result.error === 'NXDOMAIN' ||
result.error === 'ENOTFOUND') {
return result;
}
// Tentar novamente para falhas temporárias
if (result.retryable && attempt < maxRetries - 1) {
await sleep(delays[attempt]);
continue;
}
return result;
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Considerações de Segurança
A validação MX introduz considerações de segurança que os desenvolvedores devem abordar.
Prevenção de Spoofing de DNS
Consultas DNS padrão não são criptografadas e são vulneráveis a spoofing. Considere usar DNS sobre HTTPS (DoH) para aplicações sensíveis:
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);
});
}
Limitação de Taxa de Consultas DNS
Previna abuso limitando a taxa de consultas 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;
}
// Aguardar disponibilidade de token
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);
}
Usando o BillionVerify para Validação MX
Embora implementar a validação MX você mesmo forneça valor educacional, serviços profissionais de verificação de email como o BillionVerify lidam com a validação MX como parte da verificação abrangente de email.
Vantagens de Usar uma API de Verificação de Email
Verificações Abrangentes
A API de verificação de email do BillionVerify combina validação MX com verificação de sintaxe, verificação SMTP, detecção de email descartável e muito mais em uma única chamada de API. Isso elimina a necessidade de manter múltiplos sistemas de validação.
Infraestrutura Otimizada
Serviços profissionais mantêm resolvedores DNS distribuídos globalmente, gerenciam cache em escala e otimizam o desempenho em milhões de verificações.
Atualizações Contínuas
Configurações de servidores de email mudam constantemente. Serviços de verificação de email atualizam continuamente seus bancos de dados de provedores conhecidos, domínios descartáveis e padrões de servidores de email.
Exemplo de Integração de 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();
// Informações MX estão incluídas na resposta
console.log('MX Valid:', result.mx_found);
console.log('Domain Valid:', result.domain_valid);
console.log('Is Deliverable:', result.is_deliverable);
return result;
}
Conclusão
A validação de registros MX é um componente fundamental da verificação de email que confirma se um domínio pode receber email antes de tentar verificações mais intensivas em recursos. Ao implementar a validação MX adequada, você pode filtrar rapidamente domínios inválidos, otimizar o desempenho da verificação e construir aplicações de tratamento de email mais confiáveis.
Principais conclusões para validação de registros MX:
- Sempre verifique os registros MX antes de tentar verificação SMTP para economizar tempo e recursos
- Gerencie o fallback de registro A de acordo com os padrões RFC para domínios sem registros MX
- Implemente cache para reduzir a sobrecarga de pesquisa DNS para validações repetidas
- Reconheça padrões comuns para identificar provedores de email e riscos potenciais
- Trate erros graciosamente com timeouts, retries e mensagens de erro adequadas
Seja construindo um sistema personalizado de verificação de email ou integrando com um serviço como o BillionVerify, compreender registros MX ajuda você a construir melhor tratamento de email em suas aplicações. Comece a implementar a validação MX hoje e dê o primeiro passo em direção à verificação abrangente de email.