# 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`](https://www.nuget.org/packages/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

1. A **verified domain** for your `from` address — publish its SPF/DKIM/DMARC records first. ([guide](https://www.mailblastr.com/docs/domains/managing))
2. An **API key** (`mb_...`), read from `MAILBLASTR_API_KEY`. ([Authentication](https://www.mailblastr.com/docs/authentication))
3. **.NET 8.0 or newer.** The package targets `net8.0` only, so a `net6.0`/`net7.0` or .NET Framework project cannot restore it.

## Install

```sh
dotnet add package Mailblastr --version 5.0.0
```

Drop `--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`.

**Program.cs**

```csharp
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:

```csharp
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](https://www.mailblastr.com/docs/api/emails-get) later or to correlate [webhook](https://www.mailblastr.com/docs/webhooks/overview) events.

```json
{
  "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.

```csharp
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;
    }
}
```

> **Note:** The SDK sends `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.

**Program.cs (ASP.NET Core)**

```csharp
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.

```csharp
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);
```

> **Warning:** Read the `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 of `BatchEmailMessage`. Batch items reject `Attachments` and `ScheduledAt` — send those one at a time with `EmailSendAsync`.
- Pass an `idempotencyKey` to `EmailSendAsync` to 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](https://www.mailblastr.com/docs/api/emails-send) 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](https://www.mailblastr.com/docs/webhooks/overview).
- Explore [the SDKs reference](https://www.mailblastr.com/docs/resources/sdks) 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](https://www.mailblastr.com/docs/send-with/dotnet) uses only `IHttpClientFactory`.
