Verificación de email en .NET
Este tutorial integra Mailbeam en una aplicación de ASP.NET Core con patrones idiomáticos de .NET: un HttpClient tipado, el patrón de opciones para la configuración, inyección de dependencias y un atributo de validación reutilizable. Funciona igual en minimal APIs y en controladores MVC.
Qué vas a construir
- Un servicio
IEmailVerifierrespaldado por unHttpClienttipado - Un atributo de validación
[VerifiedEmail]para el enlace de modelos - Un endpoint de registro con minimal API que rechaza las direcciones no entregables
Requisitos previos
- SDK de .NET 8 o posterior
- Una clave de API de Mailbeam (regístrate gratis)
- Conocimientos básicos de inyección de dependencias en ASP.NET Core
Paso 1 — Configura la clave
Guarda la clave en los secretos de usuario en local y en variables de entorno o Key Vault en producción:
dotnet user-secrets init
dotnet user-secrets set "Mailbeam:ApiKey" "mb_live_xxxxxxxxxxxxxxxxxxxx"Enlázala con el patrón de opciones:
// Options/MailbeamOptions.cs
public sealed class MailbeamOptions
{
public string ApiKey { get; set; } = "";
public int MinScore { get; set; } = 60;
}Paso 2 — Registra un HttpClient tipado
// Program.cs
builder.Services.Configure<MailbeamOptions>(
builder.Configuration.GetSection("Mailbeam"));
builder.Services.AddHttpClient<IEmailVerifier, MailbeamVerifier>((sp, client) =>
{
var opts = sp.GetRequiredService<IOptions<MailbeamOptions>>().Value;
client.BaseAddress = new Uri("https://api.mailbeam.dev/");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", opts.ApiKey);
client.Timeout = TimeSpan.FromSeconds(5);
});Paso 3 — Construye el servicio de verificación
// Services/IEmailVerifier.cs
public interface IEmailVerifier
{
Task<bool> IsAcceptableAsync(string email, CancellationToken ct = default);
}
// Services/MailbeamVerifier.cs
public sealed class MailbeamVerifier : IEmailVerifier
{
private readonly HttpClient _http;
private readonly MailbeamOptions _opts;
private readonly ILogger<MailbeamVerifier> _log;
public MailbeamVerifier(
HttpClient http,
IOptions<MailbeamOptions> opts,
ILogger<MailbeamVerifier> log)
{
_http = http;
_opts = opts.Value;
_log = log;
}
private sealed record VerifyResponse(bool Valid, int Score, string? Reason);
public async Task<bool> IsAcceptableAsync(string email, CancellationToken ct = default)
{
try
{
var resp = await _http.PostAsJsonAsync(
"v1/verify", new { email = email.Trim().ToLowerInvariant() }, ct);
resp.EnsureSuccessStatusCode();
var result = await resp.Content.ReadFromJsonAsync<VerifyResponse>(ct);
return result is not null
&& result.Valid
&& result.Score >= _opts.MinScore;
}
catch (Exception ex)
{
// Falla en abierto: un error de la API no debería bloquear los registros
_log.LogError(ex, "Ha fallado la verificación de Mailbeam para {Email}", email);
return true;
}
}
}Paso 4 — Añade un atributo de validación
En MVC y en el enlace de modelos, un atributo propio mantiene la validación declarativa:
// Validation/VerifiedEmailAttribute.cs
public sealed class VerifiedEmailAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(
object? value, ValidationContext context)
{
if (value is not string email || string.IsNullOrWhiteSpace(email))
return ValidationResult.Success; // deja los vacíos para [Required]
var verifier = context.GetRequiredService<IEmailVerifier>();
// Los atributos son síncronos; bloquea brevemente en la llamada asíncrona
var ok = verifier.IsAcceptableAsync(email).GetAwaiter().GetResult();
return ok
? ValidationResult.Success
: new ValidationResult("Escribe una dirección de email válida y entregable.");
}
}Paso 5 — Verifica en un endpoint de minimal API
// Program.cs
app.MapPost("/api/signup", async (
SignupRequest req,
IEmailVerifier verifier,
CancellationToken ct) =>
{
if (!await verifier.IsAcceptableAsync(req.Email, ct))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["email"] = ["Escribe una dirección de email válida y entregable."]
});
var user = await CreateUserAsync(req.Email, req.Password, ct);
return Results.Created($"/users/{user.Id}", user);
});
public record SignupRequest(string Email, string Password);Paso 6 — Añade caché
Usa IMemoryCache (o una caché distribuida) para evitar consultas repetidas:
public async Task<bool> IsAcceptableAsync(string email, CancellationToken ct = default)
{
var key = $"mb:{email.Trim().ToLowerInvariant()}";
if (_cache.TryGetValue<bool>(key, out var cached))
return cached;
// ... haz la llamada de verificación como en el paso 3 ...
var ok = result is not null && result.Valid && result.Score >= _opts.MinScore;
_cache.Set(key, ok, TimeSpan.FromHours(24));
return ok;
}Pruebas
Simula el cliente tipado o apunta los tests a los dominios de prueba de Mailbeam, que son deterministas y no consumen cuota:
[Fact]
public async Task Disposable_Email_Is_Rejected()
{
var verifier = CreateVerifier(); // conectado a la configuración de test
var ok = await verifier.IsAcceptableAsync("temp@disposable.mailbeam-test.dev");
Assert.False(ok);
}
[Fact]
public async Task Valid_Email_Is_Accepted()
{
var verifier = CreateVerifier();
var ok = await verifier.IsAcceptableAsync("user@valid.mailbeam-test.dev");
Assert.True(ok);
}Buenas prácticas
| Práctica | Por qué |
|---|---|
HttpClient tipado con AddHttpClient | Pool de conexiones, resiliencia y simulación fácil |
| Patrón de opciones para la clave | Configuración limpia, sin cadenas mágicas |
| Fallar en abierto ante excepciones | Una caída de la API no rompe los registros |
Poner un Timeout de 5 s | No dejes la petición colgada en un sondeo lento |
Cachear con IMemoryCache | Reduce las llamadas duplicadas en los reintentos |
Checklist de producción
- Clave de API en los secretos de usuario o en Key Vault, nunca en el código
- Tiempo de espera del
HttpClientconfigurado - Registro de errores en los fallos de verificación
- Caché distribuida en despliegues con varias instancias
- Dominios de prueba usados en los tests unitarios