Para validar um endereço de e-mail em C#, a maioria dos desenvolvedores recorre a
System.Net.Mail.MailAddress e segue em frente. É um primeiro passo razoável — o tipo é
embutido no framework e rejeita lixo sem precisar de uma regex feita à mão — mas ele
responde apenas à primeira das três perguntas que a validação de verdade faz. O
endereço tem o formato correto? O domínio dele consegue receber e-mail? A caixa postal de fato
existe? A biblioteca base do .NET responde a primeira com clareza, precisa de um
pacote NuGet bem conhecido para a segunda e deixa a terceira para um problema de rede
que vale a pena delegar. Este guia constrói a validação de e-mail em C# como três
camadas, com código async/await idiomático para cada uma, e mostra exatamente onde o
MailAddress para.
A resposta curta#
Use MailAddress.TryCreate para a sintaxe, o pacote DnsClient.NET para a consulta de
MX e uma API de verificação para a checagem SMTP da caixa postal — o mais barato primeiro,
com curto-circuito assim que uma camada for decisiva. Não abra sockets SMTP crus
da sua aplicação para sondar caixas postais por conta própria: a porta 25 de saída é bloqueada na maioria dos
hosts, e a resposta depende da reputação do IP de envio e do greylisting que você não
quer reimplementar. Cada camada elimina endereços de forma mais barata do que a anterior;
só a última consegue confirmar um endereço como válido. Se você só quer um veredito para um
único endereço agora, cole-o no
verificador de e-mail gratuito e pule o código — o restante deste
guia é para conectar a checagem a uma aplicação. Para os conceitos por trás de cada etapa,
veja o que é a verificação de e-mail.
Camada 1: sintaxe com MailAddress.TryCreate#
MailAddress é a ferramenta certa para a camada um. Prefira a sobrecarga TryCreate
adicionada no .NET 5: ela retorna um bool em vez de lançar uma FormatException com
entrada inválida, então você pode usá-la como filtro sem envolver cada chamada em um try/catch.
using System.Net.Mail;
public static bool IsValidSyntax(string email)
{
return !string.IsNullOrWhiteSpace(email)
&& email.Length <= 320
&& MailAddress.TryCreate(email, out var parsed)
&& parsed.Address == email;
}
IsValidSyntax("jane@example.com"); // true
IsValidSyntax("not-an-email"); // false
IsValidSyntax("Jane <jane@example.com>"); // false
Aquela última comparação importa. MailAddress é um parser de e-mail, não um validador
estrito — ele aceita de bom grado formas com nome de exibição como Jane <jane@example.com>
e expõe a parte do endereço em parsed.Address. Comparar parsed.Address
de volta com a entrada crua rejeita essas formas e espaços em branco perdidos, que é o que você
quer de um filtro de sintaxe sim/não sobre a entrada do usuário. A guarda de 320 caracteres é
proteção extra: nada mais longo pode ser um endereço de verdade, e é mais barato rejeitar
cedo do que entregar uma string gigante para qualquer coisa mais adiante.
No .NET Framework ou em qualquer alvo mais antigo que o .NET 5, o TryCreate não existe, então
recorra ao construtor e capture a exceção:
try { _ = new MailAddress(email); /* syntax ok */ }
catch (FormatException) { /* reject */ }
Se você já usa data annotations em um modelo, o
System.ComponentModel.DataAnnotations.EmailAddressAttribute ([EmailAddress])
faz o mesmo trabalho de forma declarativa e é uma boa escolha dentro da validação de modelo
do ASP.NET Core. Qualquer que seja a sua escolha, trate uma aprovação como "vale a pena checar
direito", nunca como "válido". Uma checagem de sintaxe não sabe nada sobre DNS nem caixas postais — é o mesmo
motivo pelo qual a validação de e-mail por regex falha:
formato e entregabilidade são perguntas diferentes, uma um fato sobre a string e
a outra um fato sobre a internet.
Camada 2: o domínio consegue receber e-mail?#
Um domínio sem rota de e-mail não consegue aceitar e-mail de ninguém, então essa única consulta
elimina domínios mortos, nomes de empresa escritos errado e TLDs inventados antes de você
sequer tocar a rede de verdade. O detalhe: o .NET não tem um resolvedor de MX nativo.
O System.Net.Dns resolve registros A e AAAA, mas não MX, então a resposta idiomática
é o DnsClient.NET — um pacote NuGet amplamente usado e bem mantido, que a maioria dos projetos .NET
recorre quando precisa de DNS além das consultas de host. Instale-o e consulte o tipo de registro MX:
using System.Linq;
using DnsClient; // dotnet add package DnsClient
// LookupClient is thread-safe and caches responses — create one and reuse it.
private static readonly LookupClient Dns = new();
public static async Task<bool> HasMailRouteAsync(string domain)
{
try
{
var response = await Dns.QueryAsync(domain, QueryType.MX);
return response.Answers.MxRecords().Any();
}
catch (DnsResponseException)
{
// No reachable resolver, SERVFAIL, and similar — treat as "check later".
return false;
}
}
await HasMailRouteAsync("gmail.com"); // true
await HasMailRouteAsync("company-that-folded.com"); // false
Dois idiomas que vale a pena copiar. Crie o LookupClient uma vez e reutilize-o — ele é
thread-safe e faz cache das respostas, então uma instância por requisição simplesmente joga fora esse
cache. E separe o domínio do endereço no último @ com uma expressão de intervalo
para nunca interpretar errado um endereço que (legalmente) contém mais de
um:
var domain = email[(email.LastIndexOf('@') + 1)..];
Alguns domínios aceitam e-mail em um registro A sem MX (um MX implícito). Se você quiser
respeitar esse caso extremo, recorra ao QueryType.A quando o conjunto de MX estiver vazio —
mas para a esmagadora maioria dos endereços reais, uma checagem de MX é o filtro certo.
Um cuidado para o fluxo de cadastro: uma consulta de DNS trava esperando um resolvedor, então uma
falha transitória de DNS não deveria rejeitar categoricamente um cliente real. Trate uma consulta que falhou
como "checar depois", não como "inválido".
Camada 3: a caixa postal de fato existe?#
As camadas um e dois só conseguem eliminar um endereço. Um domínio pode publicar registros MX
perfeitos e ainda assim não ter caixa postal no endereço que você tem em mãos —
noreply-9f2x@gmail.com tem sintaxe válida em um domínio com rota de e-mail ativa, e
mesmo assim é uma caixa postal que nunca foi criada. Confirmar que uma caixa postal específica
existe significa a conversa de entrega SMTP: conectar ao host de e-mail, emitir
RCPT TO, ler a resposta e desconectar antes de enviar qualquer coisa.
Como funciona a verificação de e-mail percorre esse
pipeline completo, incluindo domínios catch-all que aceitam todo endereço e derrotam uma
sondagem ingênua.
Em princípio, você pode escrever isso em C# com um TcpClient e comandos SMTP crus.
Na prática, você não deveria rodá-lo do seu servidor de aplicação. A maioria dos provedores de nuvem
bloqueia a porta 25 de saída, então uma sondagem que funciona no seu notebook falha silenciosamente
em produção. A resposta depende da reputação do IP a partir do qual você conecta,
não apenas do endereço que você está checando. E os servidores receptores aplicam greylisting e
limitam a taxa de remetentes desconhecidos, então sondar em qualquer volume te faz ser adiado ou
colocado em blocklist. Essa é a camada que vale a pena delegar a uma infraestrutura feita para isso.
Chamando uma API de verificação a partir do C##
O endpoint de verificação da Qualisend roda o pipeline inteiro — sintaxe, DNS e a
sondagem SMTP da caixa postal — a partir de uma infraestrutura com reputação gerenciada, e retorna um
veredito em tempo real. HttpClient mais System.Text.Json é tudo o que você precisa; modele
o envelope da resposta como records e deixe o gerador de código-fonte JSON ou o desserializador
por reflexão preenchê-los:
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
public record VerifyResponse(
[property: JsonPropertyName("result")] VerifyResult Result);
public record VerifyResult(
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("score")] int Score,
[property: JsonPropertyName("reason")] string? Reason,
[property: JsonPropertyName("sub_flags")] IReadOnlyDictionary<string, bool> SubFlags);
// One HttpClient for the whole app — don't new one up per request.
private static readonly HttpClient Http = new()
{
BaseAddress = new Uri("https://api.qualisend.com/v1/"),
};
public static async Task<VerifyResult> VerifyAsync(string email)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "verify")
{
Content = JsonContent.Create(new { email }),
};
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("QUALISEND_API_KEY"));
using var response = await Http.SendAsync(request);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<VerifyResponse>();
return body!.Result; // status: deliverable | risky | undeliverable | unknown
}
POST https://api.qualisend.com/v1/verify é um formato de exemplo — confira a
referência da API em /developers para o endpoint exato, o formato da
chave com escopo e todos os campos. Leia a chave de uma variável de ambiente em vez
de embutir YOUR_API_KEY no código, e reutilize um único HttpClient para evitar esgotamento de
sockets. A resposta volta dentro de um envelope result:
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
}
}
status é o seu veredito principal — deliverable, risky, undeliverable ou
unknown. score classifica a confiança, reason explica um resultado negativo e
sub_flags detalha sinais como role, disposable, free e catch-all para você
aplicar a sua própria política (rejeitar undeliverable, segurar risky para revisão, sinalizar
disposable no cadastro).
Juntando as camadas para validar um endereço de e-mail em C##
O mais barato primeiro, parando assim que tiver uma resposta:
public static async Task<VerifyResult> ValidateEmailAsync(string email)
{
if (!IsValidSyntax(email))
return new VerifyResult("undeliverable", 0, "invalid_email",
new Dictionary<string, bool>());
var domain = email[(email.LastIndexOf('@') + 1)..];
if (!await HasMailRouteAsync(domain))
return new VerifyResult("undeliverable", 0, "invalid_domain",
new Dictionary<string, bool>());
return await VerifyAsync(email); // deliverable | risky | undeliverable | unknown
}
As duas camadas locais não custam nada e pegam a maior parte do lixo na hora; a camada da API roda só nos endereços que valem a ida e volta pela rede. Essa ordenação é o truque inteiro — o mesmo formato que você vai encontrar nas versões Node.js, Python e PHP deste guia, porque é o empilhamento de camadas, não a linguagem, que faz a validação funcionar.
Perguntas frequentes#
MailAddress ou EmailAddressAttribute bastam para validar um e-mail em C#?#
Para a sintaxe, sim — ambos são checagens sólidas de primeira camada e uma aposta melhor do que uma
regex feita à mão, com MailAddress.TryCreate te devolvendo um booleano que não lança exceção.
Mas eles validam o formato, não a entregabilidade: nenhum resolve DNS nem contata um
servidor de e-mail, então uma aprovação significa "parece um e-mail", não "vai entregar". Combine a
checagem de sintaxe com uma consulta de MX e uma verificação SMTP da caixa postal antes de confiar no
endereço.
O .NET tem uma forma nativa de consultar registros MX?#
Não. O System.Net.Dns resolve registros A e AAAA de host, mas não tem suporte a MX,
e é por isso que a abordagem padrão é o pacote NuGet DnsClient.NET. Consulte
QueryType.MX, reutilize um único LookupClient e trate um conjunto de respostas vazio — ou
uma DnsResponseException — como ausência de rota de e-mail, em vez de deixar a exceção ser lançada.
Dá para checar se uma caixa postal existe em C# sem uma API?#
Só em parte. O DnsClient.NET confirma que o domínio aceita e-mail, o que descarta domínios
mortos de forma barata. Confirmar a caixa postal em si exige uma conversa SMTP que você
pode tentar com um TcpClient, mas não deveria rodar do seu servidor de aplicação — a porta 25
de saída é amplamente bloqueada e o resultado depende da reputação do seu IP de envio.
Essa é a camada que um serviço de verificação existe para cuidar, e vale a pena
comparar provedores antes de construí-la
por conta própria.
Devo validar e-mails no cadastro ou ao limpar uma lista?#
Os dois, com profundidades diferentes. Rode a sintaxe e a consulta de MX de forma síncrona no cadastro — elas são rápidas o suficiente para bloquear a requisição e dar feedback instantâneo — e aja também sobre o veredito imediato da API ali; o guia de cadastro serverless mostra o padrão de ponta a ponta. Reserve a verificação mais profunda e em lote para limpar uma lista existente, onde a latência não importa e a minúcia sim.
Pronto para adicionar a camada SMTP? A referência da API tem o formato
exato de /verify, chaves com escopo e exemplos para copiar e colar, ou jogue um endereço no
verificador de e-mail gratuito para ver uma string aprovada pelo
MailAddress voltar como undeliverable.