Send Email from .NET and C#: HttpClient API Call or MailKit SMTP
Send email from ASP.NET Core or any .NET app on your own domain: a typed HttpClient for the API with the key in user secrets, or MailKit SMTP on port 587.
System.Net.Mail.SmtpClient still compiles, but Microsoft marks it obsolete and recommends MailKit, which is also where most .NET teams have ended up. For a web app, though, an HTTPS call through a typed HttpClient is simpler than any SMTP library: no connection pooling to think about, a JSON response with an id you can log, and nothing to open in the firewall beyond port 443. Here are both, with the key kept out of appsettings.json.
Quick answer
Register a typed HttpClient that posts {to: [..], subject, html} to https://API (a way for your app or website to send email automatically).oqumail.com/api/v1/emails with a Bearer header read from configuration (user secrets locally, an environment variable or Key Vault in production). For SMTP, use MailKit with ConnectAsync(mail.oqumail.com, 587, SecureSocketOptions.StartTls) and the mailbox login.
Option 1: a typed HttpClient
Store the key with dotnet user-secrets set "OquMail:ApiKey" "oqm_live_..." in development and as the environment variable OquMail__ApiKey in production. The client below is registered once and injected wherever mail is sent:
// OquMailClient.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;
public sealed class OquMailClient(HttpClient http)
{
public record SendRequest(string[] To, string Subject, string? Html = null,
string? Text = null, string? FromName = null);
public record SendResponse(string Id, string MessageId, string Status);
public async Task<SendResponse> SendAsync(SendRequest request, CancellationToken ct = default)
{
using var res = await http.PostAsJsonAsync("emails", request, ct);
if (res.StatusCode == System.Net.HttpStatusCode.Accepted)
return (await res.Content.ReadFromJsonAsync<SendResponse>(ct))!;
var body = await res.Content.ReadAsStringAsync(ct);
throw new HttpRequestException(
"OquMail send failed " + (int)res.StatusCode + ": " + body, null, res.StatusCode);
}
}
// Program.cs
builder.Services.AddHttpClient<OquMailClient>(client =>
{
client.BaseAddress = new Uri("https://api.oqumail.com/api/v1/");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", builder.Configuration["OquMail:ApiKey"]);
client.Timeout = TimeSpan.FromSeconds(10);
});Use it
System.Text.Json serialises the record properties in camelCase when you set the option, which the API expects (to, subject, html, fromName):
// anywhere with DI
var result = await oquMail.SendAsync(new OquMailClient.SendRequest(
To: new[] { user.Email },
Subject: "Welcome to Acme",
Html: "<p>Your account is ready.</p>",
FromName: "Acme"));
logger.LogInformation("queued {Id}", result.Id);
// Program.cs, once: camelCase JSON for the API
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase);Option 2: MailKit over SMTP
Keep SMTP if you already build MimeMessage objects with attachments and templates. Port 587 with StartTls is the pair that works; SslOnConnect is for port 465 and fails here:
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Acme", "app@yourdomain.com"));
message.To.Add(MailboxAddress.Parse(user.Email));
message.Subject = "Welcome to Acme";
message.Body = new TextPart("html") { Text = "<p>Your account is ready.</p>" };
using var client = new SmtpClient();
await client.ConnectAsync("mail.oqumail.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("app@yourdomain.com", config["OquMail:SmtpPassword"]);
await client.SendAsync(message);
await client.DisconnectAsync(true);Free business email on your own domain
OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Setup is a few DNS records; changes usually take effect within minutes, occasionally up to 48 hours. Start at oqumail.com.
Send from a background service
Do not await the send inside a controller action. Queue the job (a Channel<T> drained by a BackgroundService is enough for one instance; Hangfire or Quartz for more) and let the worker call the client, so a slow network never holds a request open. The background job guide covers pacing against the 10-per-minute burst limit.
What the API answers
- 202 Accepted: the message is queued. The JSON carries id, messageId and status; store the id so a support question can be matched to the delivery log.
- 400: the body failed validation, usually to missing or not an array, or neither html nor text present.
- 401: the key is missing, revoked or mistyped.
- 403: the mailbox is suspended, has reached its daily limit of 250, or sent more than 10 messages in a minute ("Sending too fast"). Back off; do not retry in a tight loop.
- 422: the message was blocked by content checks; the response explains why. Do not retry unchanged.
- 429: requests arrived faster than the per-key limit. Honour the Retry-After header.
Common questions
Why not System.Net.Mail.SmtpClient?
It is marked obsolete, does not support modern protocols well, and Microsoft points to MailKit. For new code use the API client or MailKit.
Where does the From address come from?
From the mailbox the API key belongs to. The request carries only fromName. To send as another address, create a key on that mailbox and a second named client.
Can I use this from a console app or a Windows service?
Yes. Construct HttpClient yourself, set the header, and call PostAsJsonAsync; nothing in the pattern depends on ASP.NET.