# .NET

Use the official [Lettermint .NET SDK](https://github.com/lettermint/lettermint-dotnet) to send email from a C# application. The SDK also supports the Team API and webhook signature verification. See [SDKs and integrations](/sdks) for other packages.

## Requirements

Before you start, you need:

- The .NET SDK for your application, targeting .NET 8 or newer
- A [Lettermint account](https://app.lettermint.co) with a [verified sending domain](/platform/domains/introduction)
- A Project API token from your [project settings](https://app.lettermint.co/projects)

## 1. Installation

Install the [Lettermint NuGet package](https://www.nuget.org/packages/Lettermint) from your application directory:

```bash
dotnet add package Lettermint
```

The package includes targets for .NET 8 and .NET 10.

## 2. Send your first email

Set `LETTERMINT_PROJECT_TOKEN` in your application's environment. Use a Project API token for email sends.

Add this code to a console application's `Program.cs`:

```csharp title="Program.cs"
using System;
using Lettermint;

var projectToken = Environment.GetEnvironmentVariable("LETTERMINT_PROJECT_TOKEN");
if (string.IsNullOrWhiteSpace(projectToken))
{
    throw new InvalidOperationException("Set LETTERMINT_PROJECT_TOKEN before sending email.");
}

using var email = LettermintClient.Email(projectToken);

var response = await email
    .From("Example <hello@yourdomain.com>")
    .To("recipient@example.com")
    .Subject("Hello from Lettermint")
    .Html("<h1>Hello!</h1><p>This is a test email.</p>")
    .Text("Hello! This is a test email.")
    .SendAsync();

Console.WriteLine($"Email ID: {response.MessageId}");
Console.WriteLine($"Status: {response.Status}");
```

Replace the sender with an address on your verified domain. Replace the recipient with your test address, then run the application:

```bash
dotnet run --project path/to/YourApp.csproj
```

Keep the email client for repeated requests. Dispose it when your application no longer needs it. Each `email.From(...)` or `email.Compose()` call creates a new email builder. Use a new builder for each email. Do not share a builder between concurrent sends.

## 3. Email options

The examples below use the `email` client from the previous section.

### Recipients, reply-to, and headers

```csharp
using System.Collections.Generic;

await email
    .From("Support <support@yourdomain.com>")
    .To("customer@example.com", "colleague@example.com")
    .Cc("manager@yourdomain.com")
    .Bcc("archive@yourdomain.com")
    .ReplyTo("help@yourdomain.com")
    .Subject("Support ticket 12345")
    .Headers(new Dictionary<string, string>
    {
        ["X-Ticket-ID"] = "12345"
    })
    .Text("Your support ticket has an update.")
    .SendAsync();
```

`Headers` sets email headers. Use `IdempotencyKey` for the HTTP idempotency header.

### Tags and metadata

```csharp
using System.Collections.Generic;
using Lettermint.Models;

await email
    .From("orders@yourdomain.com")
    .To("customer@example.com")
    .Subject("Order 12345 confirmed")
    .Text("Your order is confirmed.")
    .Tags(new SendMailRequestTagsItem
    {
        Name = "category",
        Value = "order-confirmation"
    })
    .Metadata(new Dictionary<string, string>
    {
        ["order_id"] = "12345",
        ["customer_id"] = "cust_789"
    })
    .SendAsync();
```

Use `Tags` for named tags or `Tag("order-confirmation")` for a single tag. Metadata values must be strings. Lettermint includes metadata in webhook payloads so you can connect delivery events to application records. See [tags](/platform/emails/tags) for tag options.

### Route and delivery settings

```csharp
using Lettermint.Models;

await email
    .From("notifications@yourdomain.com")
    .To("recipient@example.com")
    .Subject("Your account is ready")
    .Text("You can now sign in.")
    .Route("transactional")
    .Settings(new SendMailRequestSettings
    {
        Tls = TlsPolicy.Enforced
    })
    .SendAsync();
```

`Route` selects a route by its slug. `TlsPolicy.Enforced` requires TLS for email delivery. See [TLS](/platform/emails/tls) for delivery behavior.

### Idempotency

Set a key to prevent duplicate emails when your application repeats a send:

```csharp
await email
    .From("orders@yourdomain.com")
    .To("customer@example.com")
    .Subject("Order 12345 confirmed")
    .Text("Your order is confirmed.")
    .IdempotencyKey("order-confirmation-12345")
    .SendAsync();
```

Use the same key when you retry the same email. The SDK does not retry requests automatically. See [idempotency](/platform/emails/idempotency) for the API rules and retention window.

### File attachments

Read the file and encode its content as Base64:

```csharp
using System;
using System.IO;

var content = Convert.ToBase64String(await File.ReadAllBytesAsync("/path/to/invoice.pdf"));

await email
    .From("invoices@yourdomain.com")
    .To("customer@example.com")
    .Subject("Your invoice")
    .Text("Your invoice is attached.")
    .Attach("invoice.pdf", content, contentType: "application/pdf")
    .SendAsync();
```

For an inline image, pass `contentId` to `Attach` and use the matching `cid:` reference in the HTML body. See [inline images](/platform/emails/inline-images).

### Scheduled emails

Use a future timestamp with an explicit time zone:

```csharp
using System;

var scheduled = await email
    .From("reminders@yourdomain.com")
    .To("recipient@example.com")
    .Subject("Appointment reminder")
    .Text("Your appointment starts in one hour.")
    .ScheduledAt(DateTimeOffset.UtcNow.AddHours(1).ToString("O"))
    .SendAsync();

Console.WriteLine(scheduled.MessageId);
Console.WriteLine(scheduled.ScheduledAt);
```

The client also provides `RescheduleAsync` and `CancelAsync`. See [email scheduling](/platform/emails/scheduling) for scheduling limits and cancellation behavior.

## 4. Responses and errors

`SendAsync()` returns a typed response with `MessageId` and `Status`. API acceptance does not confirm delivery. Use [webhooks](/platform/webhooks/introduction) to receive delivery and bounce events.

Catch API failures before other SDK failures:

```csharp
using System;
using Lettermint;

try
{
    var response = await email
        .From("hello@yourdomain.com")
        .To("recipient@example.com")
        .Subject("Hello")
        .Text("This is a test email.")
        .SendAsync();

    Console.WriteLine(response.MessageId);
}
catch (LettermintApiException exception)
{
    Console.Error.WriteLine($"Lettermint rejected the request: HTTP {(int)exception.StatusCode}.");
}
catch (LettermintException exception)
{
    Console.Error.WriteLine($"The request failed: {exception.Message}");
}
```

`LettermintApiException` also exposes `ResponseBody`, with the token removed. Other SDK failures, including network, timeout, and response parsing failures, use `LettermintException`. If you cancel a request with a `CancellationToken`, it throws `OperationCanceledException`.

To change the default request timeout, pass `ClientOptions` when you create the client:

```csharp
using System;
using Lettermint;

using var emailWithTimeout = LettermintClient.Email(projectToken, new ClientOptions
{
    Timeout = TimeSpan.FromSeconds(15)
});
```

## Team API

Use a separate Team API token to manage team resources. Set it in `LETTERMINT_TEAM_TOKEN`:

```csharp
using System;
using Lettermint;

var teamToken = Environment.GetEnvironmentVariable("LETTERMINT_TEAM_TOKEN");
if (string.IsNullOrWhiteSpace(teamToken))
{
    throw new InvalidOperationException("Set LETTERMINT_TEAM_TOKEN before using the Team API.");
}

using var api = LettermintClient.Api(teamToken);
var page = await api.Domains.ListAsync();

foreach (var domain in page.Data ?? [])
{
    Console.WriteLine(domain.Domain);
}
```

Do not use a Team API token with the email client. See the [Team API quickstart](/platform/teams/team-api/quickstart) for token setup and permissions.

## Track delivery, opens, and bounces

Enable [tracking](/platform/emails/tracking/introduction) on your sending route to record opens and clicks. Subscribe to [webhooks](/platform/webhooks/introduction) for delivery and bounce events.

The SDK provides `Webhook.Verify(rawBody, signatureHeader, signingSecret)` to verify webhook signatures. Pass the original request body without changes. Invalid signatures throw `WebhookVerificationException`. See [webhook signature verification](/platform/webhooks/signing) for the header and signing secret.

## Next steps

<CardGroup cols={2}>
    <Card title="Tags" icon="tag" href="/platform/emails/tags">
        Organize emails with tags.
    </Card>
    <Card title="Webhooks" icon="webhook" href="/platform/webhooks/introduction">
        Receive delivery and bounce events.
    </Card>
    <Card title="Test addresses" icon="envelope" href="/platform/emails/sending-test-emails">
        Test delivery and bounce handling.
    </Card>
    <Card title="Team API" icon="users" href="/platform/teams/team-api/quickstart">
        Manage team resources.
    </Card>
</CardGroup>

<Card title="GitHub repository" icon="github" href="https://github.com/lettermint/lettermint-dotnet">
    Read the source code or report an issue.
</Card>
