Skip to the documentation
C#

Send an email

One message now or later, with attachments, or a hundred in one request.

Send a message

send.cs
var sent = await client.Emails.SendAsync(new Body{    ["from"] = "Acme Billing <billing@acme.com>",    ["to"] = new[] { "ada@example.com", "grace@example.com" },    ["replyTo"] = "support@acme.com",    ["subject"] = "Your September invoice",    ["html"] = "<p>The invoice is attached.</p>",    ["text"] = "The invoice is attached.",    ["tags"] = new Body { ["invoice"] = "inv_2026_09_4192" },}); Console.WriteLine($"{sent["id"]} {sent["status"]}");

to, cc and bcc take one address or a list, and an address is text such as Ada <ada@example.com> or a dictionary with email and name. The body comes from html and text, from a stored template, or from a draftId.

Every send carries an idempotency key. The client makes one for each call and reuses it when it retries, so a retried request replays the first message. Pass idempotencyKey: to make that hold across processes and restarts.

Attachments

attachments.cs
await using var invoice = File.OpenRead("invoice.pdf"); await client.Emails.SendAsync(new Body{    ["from"] = "billing@acme.com",    ["to"] = "ada@example.com",    ["subject"] = "Your September invoice",    ["text"] = "The invoice is attached.",    ["attachments"] = new[]    {        new Body { ["filename"] = "invoice.pdf", ["content"] = invoice, ["contentType"] = "application/pdf" },        new Body { ["fileId"] = "file_3f9c2a7b1e4d8f60a5c7b92d" },    },});

content takes a byte[] or a readable Stream, which the client encodes as base64, or text that is base64 already. fileId names a file uploaded with client.Files.UploadAsync(), which is how a file larger than 10 MB is sent.

Send later

schedule.cs
var scheduled = await client.Emails.SendAsync(new Body{    ["from"] = "billing@acme.com",    ["to"] = "ada@example.com",    ["subject"] = "Your invoice is due tomorrow",    ["text"] = "A reminder that the invoice is due tomorrow.",    ["scheduledAt"] = DateTimeOffset.UtcNow.AddDays(1),}); var id = scheduled["id"]!.GetValue<string>(); await client.Emails.RescheduleAsync(id, DateTimeOffset.UtcNow.AddDays(2)); var cancelled = await client.Emails.CancelAsync(id); Console.WriteLine(cancelled["status"]);

scheduledAt takes a DateTimeOffset, an ISO 8601 instant or a duration such as PT1H. A DateTimeOffset anywhere in a body is sent as an ISO 8601 instant in UTC. A scheduled message can be changed or cancelled until it goes.

Send a batch

batch.cs
var result = await client.Emails.SendBatchAsync(new[]{    new Body { ["from"] = "dispatch@acme.com", ["to"] = "ada@example.com", ["subject"] = "Order AC-4192 has shipped", ["text"] = "It is on its way." },    new Body { ["from"] = "dispatch@acme.com", ["to"] = "grace@example.com", ["subject"] = "Order AC-4193 has shipped", ["text"] = "It is on its way." },}); Console.WriteLine($"{result.Sent} sent, {result.Failed} failed"); foreach (var item in result){    if ((string?)item["status"] == "error")    {        Console.WriteLine($"{item["index"]} {item["error"]?["message"]}");    }}

A batch holds up to 100 messages and is never all or nothing: a bad address fails its own item and the rest still go. Check Failed and the status of each item instead of relying on an exception.