.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.