Tutoriál
Odesílání e-mailů z .NET
Odesílejte e-maily z ASP.NET Core a .NET přes balíček NuGet Emailitu, nebo přes SMTP s MailKit či System.Net.Mail, a ověřujte webhooky Emailitu.
Tento návod ukazuje, jak odesílat e-maily z aplikace v .NET přes oficiální balíček NuGet Emailit, jak místo toho použít MailKit nebo System.Net.Mail přes SMTP a jak ověřovat webhooky v minimálním API ASP.NET Core.
Předpoklady
- .NET 8 nebo novější.
- Ověřená odesílací doména, například
acme.com. - API klíč. Stačí klíč jen pro odesílání omezený na vaši doménu.
- Dokud váš workspace nemá produkční přístup, můžete odesílat jen na e-mailové adresy účtů členů workspace.
Nainstalujte SDK
dotnet add package EmailitNastavte API klíč
ASP.NET Core čte konfiguraci z proměnných prostředí, ve kterých __ odděluje sekce. V produkci nastavte Emailit__ApiKey:
export Emailit__ApiKey=secret_••••••••••••••••••••••••••••••••Při vývoji uchovávejte klíč v uživatelských tajných údajích (user secrets), ne v appsettings.json:
dotnet user-secrets init
dotnet user-secrets set "Emailit:ApiKey" "secret_••••••••••••••••••••••••••••••••"Odešlete e-mail
Zaregistrujte jednoho klienta a vkládejte ho do svých endpointů:
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 je synchronní; při hromadném odesílání ho spouštějte ze služby na pozadí, aby vlákna požadavků zůstala volná. Možnosti přijímají také Cc, Bcc, ReplyTo, Attachments, Template s Variables, ScheduledAt a Tracking; viz Odeslání e-mailu.
Pokud váš kód už sestavuje objekty System.Net.Mail.MailMessage, SDK je umí odeslat přes 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);Ošetřete chyby
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);
}Odesílání přes SMTP
MailKit
Microsoft pro nový kód pracující se SMTP doporučuje MailKit. Nainstalujte ho příkazem 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);Pro implicitní TLS se připojte k portu 465 s volbou SecureSocketOptions.SslOnConnect.
System.Net.Mail
Vestavěný klient stačí pro jednoduché případy. Podporuje jen STARTTLS, takže použijte port 587 (nebo 2525 či 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."));Všechny porty najdete na stránce Nastavení SMTP.
Přijímejte webhooky
Vytvořte webhook a jeho tajný klíč uložte jako Emailit:WebhookSecret. Přečtěte surové tělo požadavku, ověřte ho a pak zpracujte pole událostí:
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();
});Do 30 sekund vraťte 2xx; při jiných odpovědích se požadavek opakuje. Viz Ověření podpisu webhooků.
Tipy pro produkční provoz
- Zaregistrujte klienta jako singleton. Jeden
EmailitClientna aplikaci stačí. - Hromadnou poštu řaďte do fronty. Odesílejte z
BackgroundServicenebo z knihovny pro úlohy, jako je Hangfire, a zpomalte odesílání, abyste zůstali pod svými limity odesílání (ve výchozím stavu 2 e-maily za sekundu). - Zajistěte bezpečné opakování. Pokud po vypršení časového limitu požadavek opakujete, pošlete s
HttpClienthlavičkuIdempotency-Key; viz Idempotentní požadavky. - Odstraňujte duplicitní webhooky tak, že si uložíte každé zpracované
event_id.