.NET
Use the official Lettermint .NET SDK to send email from a C# application. The package provides typed requests and responses for the Sending API and Team API, plus webhook signature verification. See SDKs and integrations for other packages.
This guide covers SDK 2.0. If your project uses 1.x, follow the .NET SDK 2.0 upgrade guide on GitHub.
Requirements
Before you start, you need:
- The .NET SDK for your application, targeting .NET 8 or newer. The package includes builds for .NET 8 and .NET 10 and has no dependencies.
- A Lettermint account with a verified sending domain
- A Project API token from your project settings
1. Installation
Install the Lettermint NuGet package from your application directory:
Code
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:
Program.cs
Replace the sender with an address on your verified domain. Replace the recipient with your test address, then run the application:
Code
The client stores nothing about a message and is thread-safe. Create it once and share it, for example as a singleton, including across concurrent requests. Dispose() disposes the HttpClient that the SDK created. You can also pass the token as a string, as in new LettermintClient(token). If a token is missing or has an unrecognised format, the constructor throws LettermintConfigException and never includes the token in the message.
The Team API uses a separate Team API token. Set TeamToken alone or next to SendingToken. Each part of the client uses its own token and never falls back to the other. See Team API below and the Team API quickstart.
Use the client with dependency injection
Register the client once as a singleton. Pass an HttpClient from IHttpClientFactory if you want to manage handlers yourself. Its handler must not follow redirects, and it must not set default Authorization or x-lettermint-token headers:
Code
The SDK has no dependency on Microsoft.Extensions.*, so it does not ship an AddLettermint() extension. If you do not pass an HttpClient, the SDK uses its own, and that is also fine as a singleton.
3. Email options
The examples below use the lettermint client from the previous section. lettermint.Emails.Compose() returns an immutable builder. Every setter returns a new builder and leaves the original unchanged, so one builder is safe to share and reuse. Keep the value that a setter returns when you build an email over several statements:
Code
Each builder example is equivalent to a SendMailRequest passed to lettermint.Emails.SendAsync(). Use whichever reads better. Build() returns the builder's message as a SendMailRequest.
Recipients, reply-to, and headers
Code
Headers sets email headers. Set an idempotency key in the options of the send call instead.
Tags and metadata
Code
Use Tags for named tags or Tag("order-confirmation") for the legacy single tag. Metadata values must be strings. Lettermint includes metadata in webhook payloads but does not add it to the email headers, so you can connect delivery events to application records.
Tags accepts up to 20 name and value pairs. The SDK checks names and values before it sends the request and throws LettermintValidationException with a Field if one is invalid. A rejected tag leaves the builder unchanged. See tags for the tag rules.
Route and delivery settings
Code
Route selects a route by its slug. TlsPolicy.Enforced requires TLS for email delivery. See TLS for delivery behavior.
Idempotency
Pass an idempotency key so a retry never sends the same email twice. If a request with the same key is repeated within the idempotency window, Lettermint returns the original result instead of sending again. The key is an option of the call that sends the email:
Code
Emails.SendAsync(message, options) and Emails.SendBatchAsync(messages, options) take the same IdempotentRequestOptions. The key applies only to that call, and the SDK never retries on its own. Reuse the same key when you retry the same email. See idempotency for the API rules and retention window.
File attachments
Pass raw bytes, and the SDK base64-encodes them. To pass content that is already base64, use EmailAttachment.FromBase64:
Code
For an inline image, set ContentId and use the matching cid: reference in the HTML body:
Code
See inline images.
Scheduled emails
Pass a DateTimeOffset, an ISO 8601 string with an explicit time zone, or an English phrase such as tomorrow 9am:
Code
Change or cancel a scheduled email with lettermint.Messages.RescheduleAsync(messageId, new RescheduleMessageRequest { ScheduledAt = "2026-10-20T09:00:00Z" }) and lettermint.Messages.CancelAsync(messageId). Both accept either token: the team token if you set one, otherwise the sending token. See email scheduling for scheduling limits and cancellation behavior.
Batch sending
Send up to 500 emails in one request. The list can contain SendMailRequest records or builders:
Code
The result list has one SendMailResponse for each message, in the order you sent them.
4. Send an order confirmation
A real transactional send usually pulls several of these features together. This example sends a receipt with an HTML and plain-text body, tags it for analytics, attaches the order and customer IDs as metadata, and uses an idempotency key so a retry never emails the customer twice:
OrderConfirmation.cs
The metadata you attach here travels with every webhook event for the message, so a later delivery or bounce maps straight back to the order in your database.
5. Response
SendAsync() returns a SendMailResponse with MessageId and Status. When you schedule an email, ScheduledAt is set too. API acceptance does not confirm delivery. Use webhooks to receive delivery and bounce events.
Code
Status is a MessageStatus. Enums in the SDK are open structs: a value that the API adds later still decodes and keeps its raw string. Compare with ==, as in response.Status == MessageStatus.Scheduled, and give switch statements a default branch.
6. Handle errors
Every exception the SDK throws extends LettermintException. API errors extend LettermintApiException, which carries StatusCode, Code, Message, Details, and Body. Catch the subclasses you want to handle first:
Code
The SDK never retries on its own. Reuse the same idempotency key when you retry so the customer gets one email. Other exceptions include AuthenticationException (401), PermissionException (403), NotFoundException (404), ConflictException (409), ServerException (5xx), ConnectionException, UnexpectedResponseException for an empty body or an HTML error page, RedirectException, LettermintConfigException for a missing token or invalid option, and LettermintValidationException when the SDK rejects a request before it sends it.
If you cancel a request with a CancellationToken, the call throws OperationCanceledException, as usual in .NET. Lettermint.AuthenticationException has the same simple name as System.Security.Authentication.AuthenticationException, so qualify it if a file imports both namespaces.
Timeouts and cancellation
The default timeout is 30 seconds and covers the response headers and the body. Change it for the client with the Timeout option, or for one call with RequestOptions. Every method takes the options and a CancellationToken as its last two arguments:
Code
A timeout throws LettermintTimeoutException, so it does not clash with System.TimeoutException.
Team API
Use a separate Team API token to manage team resources. Set it in LETTERMINT_TEAM_TOKEN. Pass it as TeamToken, alone or next to SendingToken, to the same client:
Code
Query parameters are typed records. Every list response is a CursorPage<T> with Data and NextCursor. Every list also has an IterateAsync() method, an IAsyncEnumerable<T> that follows NextCursor until the last page:
Code
Stop early with break. The SDK requests the next page only when your loop reaches it.
A Team API token cannot send email. A client that has only a team token throws LettermintConfigException when you call a method of lettermint.Emails, before it sends a request. See the Team API quickstart for token setup and permissions.
Track delivery, opens, and bounces
Enable tracking on your sending route to record opens and clicks. Subscribe to webhooks for delivery and bounce events.
The SDK verifies each delivery with new Webhook(secret).Verify(rawBody, headers). Pass the original request body without changes. It throws WebhookVerificationException with a Reason when the delivery is not genuine. See Webhook signatures for a full handler and the headers it needs.
Next steps
Tags
Organize emails with tags.
Webhooks
Receive delivery and bounce events.
Test addresses
Test delivery and bounce handling.
Team API
Manage team resources.
GitHub repository
Read the source code or report an issue.