ASP.NET Core + MailKite
ASP.NET Core Identity defines IEmailSender (and, since .NET 8, the generic
IEmailSender<TUser>) specifically so account-confirmation and
password-reset mail can be swapped in with one interface implementation.
MailKite.AspNetCore is that implementation — install it, set an API key and a
from address, and every Identity email goes out through MailKite.
What you need
- A verified domain with SPF + DKIM published
- Your API key (
mk_live_…) - ASP.NET Core with Identity —
dotnet new webapp -au Individual(Razor Pages UI) orAddIdentityApiEndpoints(.NET 8+ minimal API)
Install
dotnet add package MailKite.AspNetCore
Targets net8.0+ and depends on the official
MailKite .NET SDK.
Configure
Register the sender in Program.cs — this wires up both Identity email
interfaces at once:
// Program.cs
using MailKite.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
// Registers both IEmailSender (scaffolded Identity Razor Pages UI) and
// IEmailSender<TUser> (.NET 8+ MapIdentityApi<TUser>()) against one MailKite client.
builder.Services.AddMailKiteEmailSender(builder.Configuration, options =>
{
options.FromAddress = "hello@yourdomain.com";
options.FromName = "Your App";
});
builder.Services.AddRazorPages();
var app = builder.Build();
app.MapRazorPages();
app.Run(); // appsettings.json
{
"MailKite": {
"FromAddress": "hello@yourdomain.com",
"FromName": "Your App"
}
} # Environment (never commit this)
MAILKITE_API_KEY=mk_live_...
That's it — every account-confirmation and password-reset email Identity sends now goes
out through POST /v1/send.
.NET 8+ minimal API Identity
Apps using MapIdentityApi<TUser>() instead of the scaffolded Razor Pages
UI get the same wiring — AddMailKiteEmailSender registers
IEmailSender<TUser> too:
// Program.cs — .NET 8+ minimal API Identity
builder.Services.AddIdentityApiEndpoints<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddMailKiteEmailSender(builder.Configuration, options =>
{
options.FromAddress = "hello@yourdomain.com";
});
var app = builder.Build();
app.MapIdentityApi<ApplicationUser>(); // resolves IEmailSender<ApplicationUser> → MailKite Send email directly (outside Identity)
For anything beyond Identity's account flows, inject MailKite.MailKiteClient
(registered as a singleton by AddMailKiteEmailSender) and call
Send() directly:
// Anywhere in the app — inject MailKite.MailKiteClient directly for non-Identity email
using MailKite;
public class InvoiceService(MailKiteClient mailkite)
{
public Task SendReceiptAsync(string to, string invoiceId) =>
mailkite.Send(new
{
from = "billing@yourdomain.com",
to,
subject = $"Receipt #{invoiceId}",
html = $"<p>Thanks! Receipt #{invoiceId} is attached.</p>",
});
} Receive inbound email
MailKite can POST inbound email to a minimal API route.
MailKiteClient.VerifyWebhook is a local HMAC-SHA256 check — no network call:
// Minimal API inbound webhook handler
app.MapPost("/webhooks/mailkite", async (HttpRequest request, IConfiguration config) =>
{
request.EnableBuffering();
using var reader = new StreamReader(request.Body, leaveOpen: true);
var rawBody = await reader.ReadToEndAsync();
request.Body.Position = 0;
var signature = request.Headers["x-mailkite-signature"].ToString();
var secret = config["MailKite:WebhookSecret"]!;
if (!MailKiteClient.VerifyWebhook(signature, rawBody, secret))
return Results.Unauthorized();
// Process the inbound email (rawBody is the JSON payload) — create a ticket, log, etc.
return Results.Text(MailKiteClient.ReplyOk(), "application/json");
}); What maps where
| ASP.NET Core Identity | MailKite send field |
|---|---|
MailKiteEmailSenderOptions.FromAddress/FromName | from |
SendEmailAsync's email / IEmailSender<TUser>'s email | to |
SendEmailAsync's subject | subject |
SendEmailAsync's htmlMessage | html |
Non-2xx API responses (unverified domain, suppressed recipient, rate limit) throw
MailKiteException with the API's own error message and HTTP status — Identity's
email hooks see the real failure instead of a silent no-op.
Troubleshooting
- App fails to start with a MailKite options validation error —
ApiKeyandFromAddressare validated at startup (.ValidateOnStart()). SetMAILKITE_API_KEYandMailKite:FromAddress. - Confirmation/reset emails never arrive — check that
FromAddressis on a domain verified (SPF + DKIM) with MailKite; unverified-domain sends are rejected by the API and surface as a thrownMailKiteExceptionin your logs. - Registered a custom
IEmailSenderand MailKite isn't used —AddMailKiteEmailSenderusesReplace, so whichever is registered *last* wins if you also register your own; call order between it and your own registration matters (Identity's own default doesn't, since MailKite always replaces that).
Full package reference: MailKite.AspNetCore on GitHub (once published) · MailKite .NET SDK for direct API access · Inbound webhooks for the full payload.