Tutorial
Enviar emails con .NET
Envía emails desde ASP.NET Core y .NET con el paquete NuGet de Emailit, MailKit o System.Net.Mail por SMTP, y verifica los webhooks de Emailit.
En esta guía se explica cómo enviar emails desde una aplicación .NET con el paquete NuGet oficial Emailit, cómo usar en su lugar MailKit o System.Net.Mail por SMTP y cómo verificar los webhooks en una minimal API de ASP.NET Core.
Requisitos previos
- .NET 8 o posterior.
- Un dominio de envío verificado, por ejemplo
acme.com. - Una clave de API. Basta con una clave Sending Only limitada a tu dominio.
- Hasta que tu espacio de trabajo tenga acceso de producción, solo puedes enviar a las direcciones de email de las cuentas de los miembros del espacio de trabajo.
Instalar el SDK
dotnet add package EmailitConfigurar la clave de API
ASP.NET Core lee la configuración de las variables de entorno, donde __ separa las secciones. Define Emailit__ApiKey en producción:
export Emailit__ApiKey=secret_••••••••••••••••••••••••••••••••Durante el desarrollo, guarda la clave en los secretos de usuario en lugar de en appsettings.json:
dotnet user-secrets init
dotnet user-secrets set "Emailit:ApiKey" "secret_••••••••••••••••••••••••••••••••"Enviar un email
Registra un único cliente e inyéctalo en tus endpoints:
using Emailit;
using Emailit.Options;
using Emailit.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new EmailitClient(builder.Configuration["Emailit:ApiKey"]!));
var app = builder.Build();
app.MapPost("/orders/{id}/confirm", (string id, EmailitClient emailit) =>
{
Email email = emailit.Emails.Send(new EmailSendOptions
{
From = "Acme <orders@acme.com>",
To = new[] { "ada@example.com" },
Subject = $"Order {id} confirmed",
Html = $"<p>We've received order {id}.</p>",
});
return Results.Ok(new { emailId = email.Id });
});
app.Run();Send es síncrono; para envíos masivos, ejecútalo desde un servicio en segundo plano para que los hilos de las peticiones queden libres. Las opciones también aceptan Cc, Bcc, ReplyTo, Attachments, Template con Variables, ScheduledAt y Tracking; consulta Enviar un email.
Si tu código ya crea objetos System.Net.Mail.MailMessage, el SDK puede enviarlos a través de la API:
using System.Net.Mail;
var message = new MailMessage("orders@acme.com", "ada@example.com", "Order confirmed", "<p>Thanks!</p>")
{
IsBodyHtml = true,
};
Email email = emailit.Emails.Send(message);Gestionar los errores
using Emailit.Exceptions;
try
{
emailit.Emails.Send(options);
}
catch (RateLimitException)
{
// 429: back off and retry
}
catch (AuthenticationException)
{
// 401: the API key is missing or invalid
}
catch (UnprocessableEntityException ex)
{
// 422: for example, the from domain isn't verified
logger.LogWarning("Emailit rejected the email: {Message}", ex.Message);
}
catch (ApiErrorException ex)
{
logger.LogError("Emailit error {Status}: {Body}", ex.HttpStatus, ex.HttpBody);
}Enviar por SMTP
MailKit
Microsoft recomienda MailKit para el código SMTP nuevo. Instálalo con dotnet add package MailKit:
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Acme", "orders@acme.com"));
message.To.Add(MailboxAddress.Parse("ada@example.com"));
message.Subject = "Order confirmed";
message.Body = new TextPart("html") { Text = "<p>We've received your order.</p>" };
using var smtp = new SmtpClient();
await smtp.ConnectAsync("smtp.emailit.com", 587, SecureSocketOptions.StartTls);
await smtp.AuthenticateAsync("emailit", builder.Configuration["Emailit:ApiKey"]);
await smtp.SendAsync(message);
await smtp.DisconnectAsync(true);Para TLS implícito, conéctate al puerto 465 con SecureSocketOptions.SslOnConnect.
System.Net.Mail
El cliente integrado sirve para los casos sencillos. Solo admite STARTTLS, así que usa el puerto 587 (o el 2525 o el 2587):
using System.Net;
using System.Net.Mail;
using var client = new SmtpClient("smtp.emailit.com", 587)
{
EnableSsl = true,
Credentials = new NetworkCredential("emailit", apiKey),
};
client.Send(new MailMessage("orders@acme.com", "ada@example.com", "Order confirmed", "We've received your order."));Para ver todos los puertos, consulta Configuración SMTP.
Recibir webhooks
Crea un webhook y guarda su secreto de firma como Emailit:WebhookSecret. Lee el cuerpo en bruto, verifícalo y después analiza el array de eventos:
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
app.MapPost("/webhooks/emailit", async (HttpRequest request, IConfiguration config) =>
{
using var reader = new StreamReader(request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
var signature = request.Headers["X-Emailit-Signature"].ToString();
var timestamp = request.Headers["X-Emailit-Timestamp"].ToString();
if (!long.TryParse(timestamp, out var ts) ||
Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300)
{
return Results.Unauthorized();
}
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(config["Emailit:WebhookSecret"]!));
var expected = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{body}"))).ToLowerInvariant();
if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature)))
{
return Results.Unauthorized();
}
// The body is a JSON array of up to 100 events.
using var events = JsonDocument.Parse(body);
foreach (var evt in events.RootElement.EnumerateArray())
{
if (evt.GetProperty("type").GetString() == "email.bounced")
{
var address = evt.GetProperty("data").GetProperty("object").GetProperty("to").GetString();
// Stop emailing this address.
}
}
return Results.Ok();
});Devuelve un 2xx en menos de 30 segundos; las demás respuestas se reintentan. Consulta Firma de las peticiones.
Consejos para producción
- Registra el cliente como singleton. Basta con un
EmailitClientpor aplicación. - Pon en cola los envíos masivos. Envía desde un
BackgroundServiceo una biblioteca de tareas como Hangfire, y limita el ritmo para no superar tus límites de envío (2 emails por segundo por defecto). - Haz que los reintentos sean seguros. Si reintentas después de que se agote el tiempo de espera, envía una cabecera
Idempotency-KeyconHttpClient; consulta Idempotencia. - Elimina los webhooks duplicados guardando cada
event_idque procesas.