Skip to content
Empieza con 100 créditos de verificación gratis
Qualisend
Todos los artículos
Ingeniería / 13 de junio de 2026

Cómo validar una dirección de correo en C#

8 minutes read

Qualisend team
Una ventana de código C# validando un correo a través de las capas de sintaxis, DNS y SMTP

Para validar una dirección de correo en C#, la mayoría de los desarrolladores echan mano de System.Net.Mail.MailAddress y siguen adelante. Es una primera jugada razonable —el tipo viene integrado en el framework y rechaza basura sin una expresión regular artesanal—, pero solo responde a la primera de las tres preguntas que plantea la validación real. ¿Tiene la dirección la forma correcta? ¿Puede su dominio recibir correo? ¿Existe realmente el buzón? La biblioteca de clases base de .NET responde a la primera con soltura, necesita un conocido paquete NuGet para la segunda y deja la tercera como un problema de red que conviene delegar. Esta guía construye la validación de correo en C# como tres capas, con código async/await idiomático para cada una, y muestra exactamente dónde se detiene MailAddress.

La respuesta corta#

Usa MailAddress.TryCreate para la sintaxis, el paquete DnsClient.NET para la consulta MX y una API de verificación para la comprobación del buzón por SMTP: lo más barato primero, cortocircuitando en cuanto una capa es decisiva. No abras sockets SMTP sin más desde tu aplicación para sondear buzones por tu cuenta: el puerto 25 saliente está bloqueado en la mayoría de los hosts, y la respuesta depende de la reputación de la IP de envío y del greylisting que no quieres reimplementar. Cada capa descarta direcciones de forma más barata que la anterior; solo la última puede dar por buena una dirección. Si lo único que quieres es un veredicto para una sola dirección ahora mismo, pégala en el verificador de correo gratuito y sáltate el código: el resto de esta guía es para integrar la comprobación en una aplicación. Para los conceptos que hay detrás de cada etapa, consulta qué es la verificación de correo.

Capa 1: sintaxis con MailAddress.TryCreate#

MailAddress es la herramienta adecuada para la primera capa. Prefiere la sobrecarga TryCreate añadida en .NET 5: devuelve un bool en lugar de lanzar una FormatException con entradas incorrectas, así que puedes usarla como filtro sin envolver cada llamada en un 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

Esa última comparación importa. MailAddress es un analizador de correo, no un validador estricto: acepta encantado formas con nombre para mostrar como Jane <jane@example.com> y expone la parte de la dirección en parsed.Address. Comparar parsed.Address con la entrada original rechaza esas formas y los espacios en blanco sueltos, que es justo lo que quieres de un filtro de sintaxis de sí o no sobre la entrada del usuario. La guarda de 320 caracteres es por si acaso: nada más largo puede ser una dirección real, y sale más barato rechazar pronto que pasar una cadena enorme a cualquier cosa más adelante.

En .NET Framework o cualquier destino anterior a .NET 5, TryCreate no existe, así que recurre al constructor y captura la excepción:

try { _ = new MailAddress(email); /* syntax ok */ }
catch (FormatException) { /* reject */ }

Si ya usas anotaciones de datos en un modelo, el System.ComponentModel.DataAnnotations.EmailAddressAttribute ([EmailAddress]) hace el mismo trabajo de forma declarativa y es una opción estupenda dentro de la validación de modelos de ASP.NET Core. Elijas lo que elijas, trata un aprobado como "vale la pena comprobarlo bien", nunca como "válido". Una comprobación de sintaxis no sabe nada de DNS ni de buzones: es la misma razón por la que la validación de correo con expresiones regulares falla: la forma y la entregabilidad son preguntas distintas, una es un hecho sobre la cadena y la otra un hecho sobre internet.

Capa 2: ¿puede el dominio recibir correo?#

Un dominio sin ruta de correo no puede aceptar correo para nadie, así que esta única consulta elimina dominios muertos, nombres de empresa mal escritos y TLD inventados antes de que toques la red de verdad. El inconveniente: .NET no tiene un resolvedor de MX integrado. System.Net.Dns resuelve registros A y AAAA, pero no MX, así que la respuesta idiomática es DnsClient.NET, un paquete NuGet muy usado y bien mantenido al que recurre la mayoría de los proyectos .NET cuando necesitan DNS más allá de las consultas de host. Instálalo y consulta el 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

Dos modismos que vale la pena copiar. Crea el LookupClient una sola vez y reutilízalo: es seguro para hilos y cachea las respuestas, así que una instancia por petición no hace más que tirar esa caché a la basura. Y separa el dominio de la dirección por el último @ con una expresión de rango, para no analizar mal nunca una dirección que (legalmente) contenga más de uno:

var domain = email[(email.LastIndexOf('@') + 1)..];

Algunos dominios aceptan correo en un registro A sin MX (un MX implícito). Si quieres contemplar ese caso límite, recurre a QueryType.A cuando el conjunto MX esté vacío; pero para la inmensa mayoría de las direcciones reales, una comprobación MX es el filtro adecuado. Una advertencia para un flujo de registro: una consulta DNS se bloquea sobre un resolvedor, así que un hipo transitorio de DNS no debería rechazar de forma tajante a un cliente real. Trata una consulta fallida como "comprobar más tarde", no como "no válida".

Capa 3: ¿existe realmente el buzón?#

Las capas uno y dos solo pueden descartar una dirección. Un dominio puede publicar registros MX perfectos y aun así no tener buzón en la dirección que tienes entre manos: noreply-9f2x@gmail.com es sintaxis válida en un dominio con una ruta de correo activa, y sigue siendo un buzón que nunca se creó. Confirmar que un buzón concreto existe implica la conversación de entrega SMTP: conectarse al host de correo, emitir RCPT TO, leer la respuesta y desconectarse antes de enviar nada. Cómo funciona la verificación de correo recorre esa canalización completa, incluidos los dominios catch-all que aceptan cualquier dirección y burlan un sondeo ingenuo.

En principio puedes programar esto en C# con un TcpClient y comandos SMTP en crudo. En la práctica no deberías ejecutarlo desde tu servidor de aplicación. La mayoría de los proveedores de nube bloquean el puerto 25 saliente, así que un sondeo que funciona en tu portátil falla sin ruido en producción. La respuesta depende de la reputación de la IP desde la que te conectas, no solo de la dirección que estás comprobando. Y los servidores receptores aplican greylisting y limitan la tasa a remitentes desconocidos, así que sondear con cualquier volumen hace que te aplacen o te metan en una lista de bloqueo. Esta es la capa que vale la pena delegar en una infraestructura construida para ello.

Llamar a una API de verificación desde C##

El endpoint de verificación de Qualisend ejecuta toda la canalización —sintaxis, DNS y el sondeo del buzón por SMTP— desde una infraestructura con reputación gestionada, y devuelve un veredicto en tiempo real. HttpClient más System.Text.Json es todo lo que necesitas; modela el sobre de la respuesta como records y deja que el generador de origen de JSON o el deserializador por reflexión los rellenen:

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 es una forma de ejemplo: consulta la referencia de la API en /developers para conocer el endpoint exacto, el formato de clave con ámbito y todos los campos. Lee la clave desde una variable de entorno en vez de codificar a fuego YOUR_API_KEY, y reutiliza un único HttpClient para evitar el agotamiento de sockets. La respuesta llega dentro de un sobre result:

{
  "result": {
    "status": "deliverable",
    "score": 95,
    "reason": null,
    "sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
  }
}

status es tu veredicto principal: deliverable, risky, undeliverable o unknown. score gradúa la confianza, reason explica un resultado negativo y sub_flags desglosa señales como role, disposable, free y catch-all para que apliques tu propia política (rechazar undeliverable, retener risky para revisión, marcar disposable en el registro).

Juntar las capas para validar una dirección de correo en C##

Lo más barato primero, para en cuanto tengas una respuesta:

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
}

Las dos capas locales no cuestan nada y atrapan al instante la mayor parte de la basura; la capa de la API se ejecuta solo sobre direcciones que merecen el viaje de ida y vuelta por la red. Ese orden es todo el truco: la misma forma que encontrarás en las versiones de esta guía para Node.js, Python y PHP, porque lo que hace que la validación funcione es la disposición en capas, no el lenguaje.

Preguntas frecuentes#

¿Basta con MailAddress o EmailAddressAttribute para validar un correo en C#?#

Para la sintaxis, sí: ambos son comprobaciones sólidas de primera capa y una apuesta mejor que una expresión regular artesanal, y MailAddress.TryCreate te da un booleano que no lanza excepciones. Pero validan la forma, no la entregabilidad: ninguno resuelve DNS ni contacta con un servidor de correo, así que un aprobado significa "parece un correo", no "se entregará". Combina la comprobación de sintaxis con una consulta MX y una comprobación del buzón por SMTP antes de confiar en la dirección.

¿Tiene .NET una forma integrada de consultar registros MX?#

No. System.Net.Dns resuelve registros de host A y AAAA, pero no admite MX, por eso el enfoque estándar es el paquete NuGet DnsClient.NET. Consulta QueryType.MX, reutiliza un único LookupClient y trata un conjunto de respuestas vacío —o una DnsResponseException— como ausencia de ruta de correo en lugar de dejar que lance una excepción.

¿Puedo comprobar si un buzón existe en C# sin una API?#

Solo en parte. DnsClient.NET confirma que el dominio acepta correo, lo que descarta dominios muertos de forma barata. Confirmar el buzón en sí implica una conversación SMTP que puedes intentar con un TcpClient pero que no deberías ejecutar desde tu servidor de aplicación: el puerto 25 saliente está bloqueado de forma generalizada y el resultado depende de la reputación de tu IP de envío. Esa es la capa que existe para que la gestione un servicio de verificación, y vale la pena comparar proveedores antes de construirla tú mismo.

¿Debo validar correos en el registro o al limpiar una lista?#

Ambos, con distinta profundidad. Ejecuta la sintaxis y la consulta MX de forma síncrona en el registro —son lo bastante rápidas como para bloquear la petición y dar respuesta al instante— y actúa allí también según el veredicto inmediato de la API; la guía de registro serverless muestra el patrón de principio a fin. Reserva la verificación más profunda y por lotes para limpiar una lista existente, donde la latencia no importa y la exhaustividad sí.


¿Listo para añadir la capa SMTP? La referencia de la API tiene la forma exacta de /verify, las claves con ámbito y ejemplos para copiar y pegar, o suelta una dirección en el verificador de correo gratuito para ver cómo una cadena aprobada por MailAddress vuelve como undeliverable.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started