Send emails with .NET & C#
Send email from C# with the official Mailblastr NuGet package — async, typed, BCL-only, and ready for IHttpClientFactory.
The official `Mailblastr` NuGet package (5.0.0) is the fastest way to send email from C#, F# or VB. It targets net8.0 and takes no dependency beyond the BCL — HttpClient plus System.Text.Json. Every call is async, returns a typed model, and accepts an optional CancellationToken.
You create one client and reach every operation through a flat method on the IMailblastr interface — EmailSendAsync, ContactCreateAsync, CampaignCreateAsync, and so on. Create it once and reuse it for the lifetime of the process: it holds a pooled HttpClient and is safe to share across threads.
Prerequisites
- A verified domain for your
fromaddress — publish its SPF/DKIM/DMARC records first. (guide) - An API key (
mb_...), read fromMAILBLASTR_API_KEY. (Authentication) - .NET 8.0 or newer. The package targets
net8.0only, so anet6.0/net7.0or .NET Framework project cannot restore it.
Install
dotnet add package Mailblastr --version 5.0.0Drop --version to take the newest release. The package id is Mailblastr — one capital M, the rest lower case — and the root namespace matches it, so a single using Mailblastr; brings in the client, the request models and the exception type.
Send an email
MailblastrClient.Create(apiKey) hands back the IMailblastr interface, and EmailSendAsync posts to /emails. Address your first message to delivered@mailblastr.dev, the delivery simulator: it is intercepted before it reaches a real mailbox, so it is safe to send to while you are wiring things up. Do not use example.com — it is a blocked recipient domain and the send comes back 422.
using Mailblastr;
var apiKey = Environment.GetEnvironmentVariable("MAILBLASTR_API_KEY")
?? throw new InvalidOperationException("MAILBLASTR_API_KEY is not set");
IMailblastr mailblastr = MailblastrClient.Create(apiKey);
try
{
EmailCreated sent = await mailblastr.EmailSendAsync(new EmailMessage
{
From = "Acme <hello@yourdomain.com>",
To = "delivered@mailblastr.dev",
Subject = "Hello from .NET",
HtmlBody = "<p>Sent with the official Mailblastr .NET SDK.</p>",
});
Console.WriteLine("Sent email " + sent.Id);
}
catch (MailblastrException ex)
{
// The API error envelope: { statusCode, name, message }.
Console.Error.WriteLine(ex.StatusCode + " " + ex.Name + ": " + ex.Message);
}Two property names differ from the JSON they serialize to, because C# reserves nothing useful here: the HTML body is HtmlBody (sent as html) and the plain-text alternative is TextBody (sent as text). To, Cc, Bcc and ReplyTo are EmailAddressList, which converts implicitly from a single string or a string[], so both of these compile:
To = "delivered@mailblastr.dev",
To = new[] { "delivered@mailblastr.dev", "bounced@mailblastr.dev" },Handling the response
A successful send resolves to EmailCreated, whose single Id property is the new email id. Keep it to retrieve the email later or to correlate webhook events.
{
"id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
}Any non-2xx answer throws MailblastrException carrying the envelope as StatusCode, Name and Message. Branch on Name together with StatusCode, never on Message — message text is scrubbed of provider identifiers server-side and is not a stable contract. Plan and quota rejections additionally populate Limit, reputation gates populate Reputation, and a batch that failed part way through populates Sent/SentCount; all of them are null on an ordinary error.
catch (MailblastrException ex)
{
switch (ex.Name)
{
case "validation_error":
// 422 for a bad field — or 403 when the User-Agent header is missing.
break;
case "daily_quota_exceeded":
if (ex.Limit is not null)
{
Console.Error.WriteLine(ex.Limit.Kind + " cap hit: "
+ ex.Limit.Used + "/" + ex.Limit.Limit);
}
break;
}
}User-Agent: mailblastr-dotnet/5.0.0 on every request. The API rejects a request with a missing or blank User-Agent with 403 validation_error before it even authenticates, which is why raw-HttpClient callers have to set the header themselves and SDK callers do not.Dependency injection and tuning
Pass MailblastrClientOptions to supply your own HttpClient — typically one from IHttpClientFactory — or to change the timeout and retry budget. A client you supply is never disposed by the SDK, and it needs no headers of its own: the Authorization and User-Agent headers are set per request.
builder.Services.AddHttpClient("mailblastr");
builder.Services.AddSingleton<IMailblastr>(sp =>
MailblastrClient.Create(
builder.Configuration["Mailblastr:ApiKey"]!,
new MailblastrClientOptions
{
HttpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("mailblastr"),
Timeout = TimeSpan.FromSeconds(10),
MaxRetries = 3,
}));Timeout bounds each individual attempt — a retry gets a fresh one — and defaults to 30 seconds; TimeSpan.Zero disables it. MaxRetries (default 2) is the budget for 429 and 503 responses only. No other status, no network error and no timeout is retried, so a send is never silently duplicated. When you supply your own HttpClient, remember its own HttpClient.Timeout still applies on top.
Scheduling a send
Set ScheduledAt to an ISO 8601 timestamp to queue the email instead of sending it now. Scheduled sends bypass the delivery simulator, so use a real address on your own verified domain here — delivered@mailblastr.dev is only honoured for immediate sends and would be rejected.
var sent = await mailblastr.EmailSendAsync(new EmailMessage
{
From = "Acme <hello@yourdomain.com>",
To = "you@yourdomain.com",
Subject = "Your weekly digest",
HtmlBody = "<p>Scheduled ahead of time.</p>",
ScheduledAt = "2026-09-01T09:00:00Z",
});
// Later: reschedule or cancel by id.
await mailblastr.EmailUpdateAsync(sent.Id, "2026-09-02T09:00:00Z");
await mailblastr.EmailCancelAsync(sent.Id);mb_ API key from configuration or your secrets manager and keep it server-side. Never ship it in a desktop, mobile, or Blazor WebAssembly client — the key can send email as your account.Next steps
- Send up to 100 messages in one request with
EmailBatchAsync, passing a list ofBatchEmailMessage. Batch items rejectAttachmentsandScheduledAt— send those one at a time withEmailSendAsync. - Pass an
idempotencyKeytoEmailSendAsyncto make a retry safe; replaying the key returns the original result. It is honoured by the send and batch-send routes only. - See the full Send Email API reference for every body field (
Cc,Bcc,ReplyTo,Attachments,Headers,TemplateId). - Verify inbound webhooks with
WebhookSignature.Verify(rawBody, headers, signingSecret)— pass the raw request body, never re-serialized JSON. See Webhooks. - Explore the SDKs reference for the other resources the package exposes — contacts, segments, topics, campaigns, templates, automations, domains and events.
- Prefer no package at all? Send with .NET over raw HTTP uses only
IHttpClientFactory.