Sendry Docs
SDKs

.NET SDK

Official .NET SDK for the Sendry email API.

Installation

dotnet add package Sendry

Or via the package manager console:

Install-Package Sendry

The SDK multi-targets net8.0 and netstandard2.1, so it runs on .NET 8+, .NET Framework 4.6.1+ via netstandard-compatible runtimes, Unity, Xamarin, and Mono. The only transitive dependency is System.Text.Json (when targeting netstandard2.1).

Initialization

using Sendry;
using Sendry.Models;

// Simple — just an API key
var sendry = new SendryClient("sn_live_your_api_key");

// Or with full options
var sendry = new SendryClient(new SendryOptions {
    ApiKey     = Environment.GetEnvironmentVariable("SENDRY_API_KEY")!,
    BaseUrl    = "https://api.sendry.online",
    Timeout    = TimeSpan.FromSeconds(30),
    MaxRetries = 2,
});

ASP.NET Core / dependency injection

Inject your own HttpClient so the SDK lives in the standard IHttpClientFactory lifecycle:

// Program.cs
builder.Services.AddHttpClient("sendry");
builder.Services.AddSingleton(sp =>
    new SendryClient(new SendryOptions {
        ApiKey = builder.Configuration["Sendry:ApiKey"]!,
        HttpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("sendry"),
    }));

Emails

// Send a transactional email
var result = await sendry.Emails.SendAsync(new SendEmailParams {
    From = "Acme <hello@acme.com>",
    To = "user@example.com",
    Subject = "Welcome!",
    Html = "<h1>Welcome to Acme!</h1>",
});
Console.WriteLine(result.Id); // em_abc123

// Send to multiple recipients
await sendry.Emails.SendAsync(new SendEmailParams {
    From = "hello@acme.com",
    To = new[] { "alice@example.com", "bob@example.com" },
    Subject = "Team announcement",
    Html = "<p>Big news...</p>",
});

// Send with attachments
await sendry.Emails.SendAsync(new SendEmailParams {
    From = "billing@acme.com",
    To = "user@example.com",
    Subject = "Your invoice",
    Html = "<p>Please find your invoice attached.</p>",
    Attachments = new[] {
        new EmailAttachment {
            Filename = "invoice.pdf",
            Content = Convert.ToBase64String(pdfBytes),
            ContentType = "application/pdf",
        },
    },
});

// Schedule for later
await sendry.Emails.SendAsync(new SendEmailParams {
    From = "hello@acme.com",
    To = "user@example.com",
    Subject = "Reminder",
    Html = "<p>Don't forget!</p>",
    ScheduledAt = "2026-06-01T09:00:00Z",
});

// Get an email
var email = await sendry.Emails.GetAsync("em_abc123");
Console.WriteLine(email.Status); // "delivered"

// List with cursor pagination
var page = await sendry.Emails.ListAsync(new ListEmailsParams { Limit = 25 });
foreach (var e in page.Data) Console.WriteLine(e.Id);

// Batch send
var batch = await sendry.Emails.SendBatchAsync(new SendBatchParams {
    From = "hello@acme.com",
    Emails = new List<SendEmailParams> {
        new() { To = "a@example.com", Subject = "Hi A", Html = "<p>A</p>" },
        new() { To = "b@example.com", Subject = "Hi B", Html = "<p>B</p>" },
    },
});

// Cancel a scheduled email
await sendry.Emails.CancelAsync("em_abc123");

Domains

var dom = await sendry.Domains.CreateAsync(new CreateDomainParams { Name = "example.com" });
foreach (var record in dom.DnsRecords ?? Array.Empty<DnsRecord>())
{
    Console.WriteLine($"{record.Type} {record.Name} -> {record.Value}");
}

var verify = await sendry.Domains.VerifyAsync(dom.Id);
if (verify.SpfVerified && verify.DkimVerified) Console.WriteLine("Verified!");

await sendry.Domains.UpdateAsync(dom.Id, new UpdateDomainParams {
    Tracking = new DomainTrackingConfig { Opens = false, Clicks = true },
});

Contacts & audiences

var audience = await sendry.Audiences.CreateAsync("Newsletter subscribers");

var contact = await sendry.Contacts.CreateAsync(new CreateContactParams {
    Email = "alice@example.com",
    FirstName = "Alice",
    AudienceId = audience.Id,
    Properties = new Dictionary<string, object> { ["plan"] = "pro" },
});

var page = await sendry.Contacts.ListAsync(audienceId: audience.Id);

Campaigns

var campaign = await sendry.Campaigns.CreateAsync(new Dictionary<string, object?> {
    ["name"] = "Q3 newsletter",
    ["audience_id"] = audience.Id,
    ["from"] = "news@acme.com",
    ["subject"] = "What's new",
    ["html"] = "<h1>Hi</h1>",
});

await sendry.Campaigns.SendAsync(campaign.Id);
// or .PauseAsync / .ResumeAsync / .CancelAsync

Webhooks

var wh = await sendry.Webhooks.CreateAsync(new CreateWebhookParams {
    Url = "https://your-app.com/hooks/sendry",
    Events = new[] { "email.delivered", "email.bounced", "email.opened" },
});
Console.WriteLine(wh.Secret); // store this securely

Verifying webhook signatures

Sendry signs every webhook with HMAC-SHA256 over "{timestamp}.{body}". Verify before processing:

// ASP.NET Core minimal API
app.MapPost("/webhooks/sendry", async (HttpRequest req) =>
{
    using var reader = new StreamReader(req.Body);
    var body = await reader.ReadToEndAsync();

    var signature = req.Headers["X-Sendry-Signature"].ToString();
    var timestamp = req.Headers["X-Sendry-Timestamp"].ToString();
    var secret = builder.Configuration["Sendry:WebhookSecret"]!;

    if (!WebhookVerifier.Verify(body, signature, timestamp, secret))
    {
        return Results.Unauthorized();
    }

    // Process event...
    return Results.Ok();
});

Error handling

All exceptions live under Sendry.Exceptions and derive from SendryException:

using Sendry.Exceptions;

try
{
    await sendry.Emails.SendAsync(payload);
}
catch (AuthenticationException)      { /* 401 — bad API key */ }
catch (ValidationException ex)       { /* 422 — ex.Details JsonElement */ }
catch (RateLimitException ex)        { await Task.Delay(TimeSpan.FromSeconds(ex.RetryAfter ?? 1)); }
catch (NotFoundException)            { /* 404 */ }
catch (ApiException ex)              { /* other 4xx/5xx — ex.StatusCode, ex.Code */ }
catch (NetworkException ex)          { /* timeout, DNS, socket */ }

5xx and network errors retry automatically with exponential backoff (200ms, 400ms, 800ms, capped at 5s) up to MaxRetries times.

Cancellation

All async methods accept a CancellationToken:

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await sendry.Emails.SendAsync(payload, cts.Token);

Source

The .NET SDK lives in the Sendry monorepo at packages/sendry-dotnet. Issues and pull requests are welcome on the GitHub mirror at sendry-dev/sendry-dotnet.

On this page