.NET

Install the Coss NuGet package, configure the client, handle errors and verify webhooks in C#.

The .NET SDK is the Coss package on NuGet, for .NET 8+, .NET Framework 4.6.2+ and .NET Standard 2.0. Every method is async.

dotnet add package Coss
using Coss;

var client = new CossClient(); // reads COSS_API_KEY

var email = await client.Email.SendAsync(new SendEmailBody
{
    Body = new SendEmailRequest
    {
        From = "Acme <hello@acme.com>",
        To = new[] { "ada@example.com" },
        Subject = "Welcome to Acme",
        Html = "<p>Thanks for signing up!</p>",
    },
});
Console.WriteLine($"{email.Id} {email.Status}");

Requests are records with properties named like the API's fields in PascalCase (ReplyTo, ScheduledAt). The response models are in the Coss namespace; the email model is EmailMessage (Coss.Email holds the email methods' requests).

Options

new CossClient(apiKey, apiVersion, new ClientOptions { … }):

Every method also takes RequestOptions and a CancellationToken for that call only.

Errors

A failed request throws an exception named after its status (NotFoundError, UnprocessableEntityError, …) whose Body is the API's error envelope. They all derive from CossClientApiException (StatusCode, Body); a request that never got a response throws CossClientException.

try
{
    await client.Email.GetAsync(new GetEmailRequest { Id = "email_01k7c8m4q2f3e9v6t5r1n0p8yz" });
}
catch (NotFoundError e)
{
    Console.WriteLine($"{e.StatusCode} {e.Body.Error.Code} {e.Body.Error.RequestId}");
}

Idempotency and lists

Requests of endpoints that take an Idempotency-Key have an IdempotencyKey property; every other POST gets a random key of its own, reused by its retries.

List methods return one page. Pass the last Id as After while HasMore is true:

var request = new ListEmailRequest { Limit = 100 };
while (true)
{
    var page = await client.Email.ListAsync(request);
    foreach (var email in page.Data) Console.WriteLine(email.Id);
    if (!page.HasMore) break;
    request = request with { After = page.Data.Last().Id };
}

Webhooks

Webhooks.Verify checks a webhook request's signature and age and returns the parsed event (a JsonElement); it throws WebhookVerificationException when you must reject the request. Pass the raw body exactly as received and the headers as name/value pairs. Webhooks.VerifyLogDrainBatch does the same for log drains and returns the batch's lines.

app.MapPost("/webhooks/coss", async (HttpRequest request) =>
{
    var body = await new StreamReader(request.Body).ReadToEndAsync();
    var headers = request.Headers.Select(h => KeyValuePair.Create(h.Key, h.Value.ToString()));
    try
    {
        var e = Webhooks.Verify(body, headers, Environment.GetEnvironmentVariable("COSS_WEBHOOK_SECRET")!);
        Console.WriteLine(e.GetProperty("type").GetString());
        return Results.NoContent();
    }
    catch (WebhookVerificationException)
    {
        return Results.BadRequest();
    }
});