Send email with .NET

A typed HttpClient registered once, injected everywhere.

You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.

Send

public sealed class SendingClient(HttpClient http)
{
    public Task<HttpResponseMessage> SendWelcomeAsync(string to, string userId) =>
        http.PostAsJsonAsync("/api/v1/emails", new
        {
            from = "Acme <[email protected]>",
            to,
            subject = "Welcome",
            html = "<p>You're in.</p>",
            idempotencyKey = $"welcome-{userId}"
        });
}
 
// Program.cs
builder.Services.AddHttpClient<SendingClient>(client =>
{
    client.BaseAddress = new Uri("https://sending.dev");
    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", builder.Configuration["Sending:ApiKey"]);
});

A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.

What changes in .NET

AddHttpClient gives you connection reuse and a place to hang retry policies, which is the difference between a demo and something that runs at three in the morning.

The thing that catches people

System.Text.Json camelCases property names by default in ASP.NET Core but not in a plain console app. If the API answers 422 on idempotencyKey, that default is why.

Next

  • All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
  • Webhooks: delivery, bounce and complaint events, signed with HMAC.
  • Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
  • MCP: the same operations as tools, when the one writing the code is an agent.

Nearby: Compiled languages

All 30 stacks