MailKite
Get your API key
Receiving email

Receive email in real time with C# / .NET

Receive email in C# / .NET with a signed JSON webhook: install the SDK, run a receiver, configure a MailKite address, and test delivery. Here, live means event-driven delivery after the email arrives, with normal SMTP and forwarding delays; it does not promise an exact latency.

1. Prepare a receiving address

In MailKite, add a domain or claim an available hosted subdomain. Complete the verification shown in the dashboard before receiving. For your own domain, publish the exact MX record MailKite returns. Choose an address such as inbound@your-receiving-domain.

For a detailed setup walkthrough, use the quickstart and domain verification docs. Your HTTP receiver needs a public HTTPS URL; a development tunnel can expose local port 3000 during testing.

The shared demo repository contains the exact receiver sources, local checks and inbox inspector used by this series.

2. Configure the webhook and run the C# / .NET receiver

Create a webhook route matching your receiving address and point it to https://your-app.example/hooks/mailkite, replacing the example host with your public HTTPS host. Copy that route's signing secret into the receiver's environment. Avoid a catch-all route if you only want mail for one address.

.NET 10 SDK and Git. The SDK is referenced from its tested public source revision because the NuGet package is not available. Set MAILKITE_WEBHOOK_SECRET to the signing secret for your configured MailKite webhook, supplied through your environment or secret manager. You do not need an API key for local signature verification.

install dependencies
dotnet new web -n Inbound --framework net10.0
git clone https://github.com/mailkite/mailkite-dotnet.git mailkite-dotnet
git -C mailkite-dotnet checkout 4b401e7865e715386e997b72efd3dac1e45ff160
cd Inbound
dotnet add reference ../mailkite-dotnet/src/MailKite/MailKite.csproj

Put Program.cs in the Inbound project. The SDK source clone is a library, not a receiver. Accept HttpRequest instead of an already-bound event DTO so the signed body is still available. MailKiteClient.VerifyWebhook needs the webhook secret, not an API key.

Program.cs
// Docs: docs/website/content/live-receiving-email-series.md
using System.Text;
using System.Text.Json;
using MailKite;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var secret = Environment.GetEnvironmentVariable("MAILKITE_WEBHOOK_SECRET")
?? throw new InvalidOperationException("Set MAILKITE_WEBHOOK_SECRET");
if (string.IsNullOrWhiteSpace(secret)) throw new InvalidOperationException("Set MAILKITE_WEBHOOK_SECRET");
app.MapPost("/hooks/mailkite", async (HttpRequest request) => {
using var buffer = new MemoryStream();
await request.Body.CopyToAsync(buffer);
var raw = Encoding.UTF8.GetString(buffer.ToArray());
var signature = request.Headers["x-mailkite-signature"].ToString();
if (!MailKiteClient.VerifyWebhook(signature, raw, secret)) return Results.Unauthorized();
try {
using var json = JsonDocument.Parse(raw);
var item = json.RootElement;
if (item.ValueKind != JsonValueKind.Object || !item.TryGetProperty("type", out var type)
|| type.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(type.GetString()))
return Results.BadRequest();
if (type.GetString() == "email.received") {
if (!item.TryGetProperty("id", out var id) || id.ValueKind != JsonValueKind.String
|| string.IsNullOrWhiteSpace(id.GetString())) return Results.BadRequest();
Console.WriteLine("received " + id.GetString());
}
// Demonstration only: persist/enqueue and deduplicate before production acknowledgement.
return Results.Ok(new { status = "ok" });
} catch (JsonException) { return Results.BadRequest(); }
});
app.Run();

Download the exact receiver shown above. Save it beside its dependencies, then start it with the environment variable already set:

run locally
dotnet run --no-launch-profile --urls http://127.0.0.1:3000

This is a verification-and-logging demonstration, not a durable inbox. Before production, insert the event into a database or durable queue using its id as a unique key, and only then acknowledge it. Logging followed by a 200 response can lose work if the process stops.

3. Test the exact receiver and address

Download the local signed test and run it with Node.js and the same webhook secret. It creates synthetic HTTP requests only, including a duplicate id and a tampered body; it sends no email. The first two requests should return the SDK's JSON acknowledgement, and the tampered one should return 401.

test the local receiver
node check.mjs http://127.0.0.1:3000/hooks/mailkite

Then send a real message to the exact receiving address to test routing. The dashboard's domain-webhook test uses a catch-all route; it does not test an exact-address route. See signature verification and delivery and retries.

4. Check a real message and its payload

Send a new message to the MailKite receiving address. Confirm delivery in MailKite and receipt at the endpoint. The JSON event has type: "email.received", a stable id, sender and recipient address objects, decoded bodies, attachment metadata and authentication results.

  • Body: use text, falling back to textFromHtml when the plain-text part is null.
  • Attachments: process the signed download URLs after durable acceptance, before they expire.
  • Authentication: the webhook signature authenticates MailKite's delivery. It does not prove the original sender is trustworthy. Forwarding can change email-authentication results.
  • Retries: replay a delivery and confirm your production handler does not create a second job for the same message id.

Troubleshoot the delivery boundary

SymptomCheck
No message in MailKiteReceiving-domain verification, MX records, the destination address, and the provider's forwarding settings.
Message stored, no webhookThe route's exact address match and enabled webhook destination.
401 responseThe signing secret, raw body, signature header and server clock. Do not disable freshness checks to hide the problem.
503 responseDatabase availability, persistent storage and write permissions. Replay after correcting the storage failure.
Duplicate application workUnique message ids, queue deduplication and downstream side-effect idempotency.

Continue with the complete event reference, other receiving guides or the companion implementation article.