---
title: "Send an email in C#"
url: https://recursiveai.org/onemail/cookbook/send-email/
description: "Send an email in C# over SMTP: a plain text and HTML body, an attachment and a non-ASCII sender name, with STARTTLS required and a result for every recipient. Runnable code with OneMail.Client 1.0.1."
version: "1.0.1"
---

# Send an email in C#

Describe the message with `OutgoingMessage` and pass it to `Mail.SendAsync` with the server and your login. It connects, upgrades the connection with STARTTLS, logs in with the strongest mechanism the server offers, sends and says goodbye, in one call. The program below sends the same message as [Create an EML file](https://recursiveai.org/onemail/cookbook/create-eml/): two bodies, a CSV attachment and a sender name with accents.

```shell
dotnet new console -o SendEmail && cd SendEmail
dotnet add package RecursiveAI.OneMail.Client --version 1.0.1
```

`RecursiveAI.OneMail.Client` brings in `RecursiveAI.OneMail`. Put this in `Program.cs`:

```csharp
using System.Text;
using RecursiveAI.OneMail;
using RecursiveAI.OneMail.Client.Sasl;
using RecursiveAI.OneMail.Client.Smtp;
using RecursiveAI.OneMail.Composition;

// The server and the login come from the environment
string host = Environment.GetEnvironmentVariable("SMTP_HOST") ?? "smtp.example.com";
int port = int.Parse(Environment.GetEnvironmentVariable("SMTP_PORT") ?? "587");
var login = new SaslCredentials
{
    UserName = Environment.GetEnvironmentVariable("SMTP_USER"),
    Password = Environment.GetEnvironmentVariable("SMTP_PASSWORD"),
};

// Non-ASCII names and subjects are encoded for you; Date and Message-ID are added
var message = new OutgoingMessage
{
    From = "Zoë Martín <zoe@example.com>",
    To = ["Sam Lee <sam@example.org>"],
    Subject = "Q3 report – final",
    Text = "Hi Sam,\r\n\r\nThe report is attached.\r\n",
    Html = "<p>Hi Sam,</p><p>The report is attached.</p>",
    Attachments = [new OutgoingAttachment("q3.csv", Csv(), "text/csv")],
};

// Connect, STARTTLS (required by default), log in, send, QUIT
SmtpSendResult result = await Mail.SendAsync(message,
    new SmtpClientOptions { Host = host, Port = port, Credentials = login });

// A refused recipient is data in the result, not an exception
foreach (SmtpRecipientResult r in result.Recipients)
{
    string verdict = r.Accepted ? "accepted" : "refused";
    Console.WriteLine($"{r.Address}: {verdict} ({r.Reply.Code})");
}
Console.WriteLine(result.Delivered
    ? $"Queued: {result.DataReply!.Code} {result.DataReply.Text}"
    : "Not accepted");
Console.WriteLine($"Sent {result.Size:N0} bytes, chunking: {result.UsedChunking}");

static byte[] Csv() =>
    Encoding.UTF8.GetBytes("month,revenue\r\nJul,1200\r\nAug,1350\r\n");
```

Set `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER` and `SMTP_PASSWORD`, then `dotnet run`. Against a local [Mailpit](https://mailpit.axllent.org/) test server with STARTTLS required, it printed:

```text
sam@example.org: accepted (250)
Queued: 250 2.0.0 Ok: queued as 1smy8cZobLtp06wdVBlgVo
Sent 916 bytes, chunking: False
```

The server received a `multipart/mixed` message with a text and an HTML part and the attachment. The sender’s name went out as `=?utf-8?B?Wm/DqyBNYXJ0w61u?=` and the dash in the subject as an encoded word, so any mail program shows them as written. Your server’s queue id will differ.

## Security by default

`SmtpClientOptions.Security` starts at `StartTls`. In the docs’ words: “STARTTLS (RFC 3207) is required. If the server does not announce it, `SmtpFeatureRequiredException("STARTTLS")` is thrown and no credentials are sent.” For port 465, set `Security = SmtpSecurity.ImplicitTls`. The server’s certificate is checked as any .NET TLS connection checks it, so a local test server needs a certificate your machine trusts.

Gmail and Outlook.com want OAuth 2.0. The library “never acquires tokens”: get one yourself and pass it as `SaslCredentials.AccessToken` instead of `Password`. Endpoints and app setup: [Gmail and Outlook.com](https://recursiveai.org/onemail/docs/providers/).

## The result

`SmtpSendResult` has the server’s reply for the sender, for each recipient and for the message. “A rejected sender or recipient is data in the result, never an exception.” `Delivered` is true when the server took the message for at least one recipient, and `Complete` when it took all of them. Call `result.ThrowIfNotDelivered()` if you prefer an exception. A `Bcc` address gets the message but isn’t in the headers that go out.

## Limits

- **Providers.** From the project’s known gaps: “Outlook.com provider rows have not been run.” “Yahoo and iCloud have not been run either.” Gmail passed over SMTP, IMAP and POP3.
- **x64 only.** “The protocol rows are x64 only. The container protocol suites have not been run on arm64.”
- **No queue.** The client “does not queue or retry: a `4xx` is reported in the result and the caller decides”, and it “does not look up MX records”: it sends to the server you name.
- **No signing.** The client “does not sign”; DKIM and S/MIME aren’t in the 1.0.1 packages.
- **Native platforms only.** The client isn’t in the browser (WebAssembly) build.

Several messages over one connection, and every option: [SMTP](https://recursiveai.org/onemail/docs/smtp/). Reading mail: [IMAP](https://recursiveai.org/onemail/docs/imap/) and [POP3](https://recursiveai.org/onemail/docs/pop3/). Writing the message to a file instead: [Create an EML file](https://recursiveai.org/onemail/cookbook/create-eml/).
