Taifa MailTaifa Mail Docs
SDKs

.NET SDK

Send email, manage domains, contacts, templates, and webhooks from .NET with the official TaifaMail.Sdk package for C#.

The official .NET SDK is published as TaifaMail.Sdk on NuGet. Source lives in the taifa-mail-sdks repository.

The package targets net8.0 and netstandard2.0, so it works across modern .NET, .NET Framework, Xamarin, and Unity. Every API call is async and returns a Task.

Installation

dotnet add package TaifaMail.Sdk

You can also add it through the NuGet Package Manager or by adding a <PackageReference> to your .csproj.

Client setup

Create an API key in the dashboard under Settings -> API Keys. Keys start with tfm_k_. Construct the client with that key:

using TaifaMail.Sdk;
 
var taifamail = new TaifaMail("tfm_k_your_api_key");

The constructor accepts these parameters:

ParameterTypeDefaultDescription
apiKeystring(required)Your API key. Starts with tfm_k_.
baseUrlstring?https://govconnect.keOverride the API base URL. A trailing slash is trimmed automatically.
httpClientHttpClient?nullSupply your own HttpClient (recommended under dependency injection). One is created and owned otherwise.
maxRetriesint3Total attempts on 429 / 5xx, including the first.
using System.Net.Http;
using TaifaMail.Sdk;
 
var http = new HttpClient();
var taifamail = new TaifaMail(
    apiKey: Environment.GetEnvironmentVariable("TAIFA_MAIL_API_KEY")!,
    baseUrl: "https://govconnect.ke",
    httpClient: http,
    maxRetries: 3);

TaifaMail implements IDisposable. It disposes the underlying HttpClient only when it created the client itself. When you pass your own HttpClient (for example one managed by IHttpClientFactory), the SDK leaves its lifetime to you.

Send an email

Emails.SendAsync queues a single message and returns its id and initial status. Use an object initializer to build the SendEmail; the To collection is initialized for you, so you can add recipients directly.

using TaifaMail.Sdk;
 
var taifamail = new TaifaMail("tfm_k_your_api_key");
 
SendEmailResult result = await taifamail.Emails.SendAsync(new SendEmail
{
    From = new Address("hello@yourdomain.com", "Your Company"),
    To = { "customer@example.com" },
    Subject = "Your receipt",
    Html = "<h1>Thanks for your order</h1><p>Your receipt is attached.</p>",
    Text = "Thanks for your order. Your receipt is attached.",
});
 
Console.WriteLine($"{result.Id} {result.Status}");

The returned SendEmailResult carries:

FieldTypeDescription
IdstringThe message id.
StatusstringInitial status, for example queued or scheduled.
MessageIdstring?RFC 5322 Message-ID, once assigned.
RejectionReasonstring?Reason the message was rejected, if it was.

A few conveniences worth knowing:

  • Address converts implicitly from a bare email string, so To = { "customer@example.com" } is shorthand for To = { new Address("customer@example.com") }. Use new Address(email, name) when you want a display name.
  • The SDK exposes a clean From property and maps it to the wire field from_ for you. You never write from_.
  • Provide Html, Text, or both.
  • SendAt is a DateTimeOffset?; set it to schedule the message for later (Starter plan and up). It is serialized as UTC ISO 8601.
await taifamail.Emails.SendAsync(new SendEmail
{
    From = "hello@yourdomain.com",
    To = { new Address("a@example.com", "Ada"), "b@example.com" },
    Cc = new List<Address> { "manager@example.com" },
    ReplyTo = new Address("support@yourdomain.com"),
    Subject = "Welcome aboard",
    Html = "<p>Welcome!</p>",
    Tags = new List<string> { "onboarding" },
    SendAt = DateTimeOffset.UtcNow.AddHours(1),
});

The client also exposes a shortcut taifamail.SendAsync(...) that forwards to Emails.SendAsync.

Emails

Accessed as client.Emails.

// Send a single email.
SendEmailResult sent = await taifamail.Emails.SendAsync(new SendEmail
{
    From = "hello@yourdomain.com",
    To = { "customer@example.com" },
    Subject = "Hello",
    Html = "<p>Hi</p>",
});
 
// Send a batch (Starter plan and up, capped by your plan).
BatchResult batch = await taifamail.Emails.SendBatchAsync(new[]
{
    new SendEmail { From = "hello@yourdomain.com", To = { "a@example.com" }, Subject = "Hi", Html = "<p>A</p>" },
    new SendEmail { From = "hello@yourdomain.com", To = { "b@example.com" }, Subject = "Hi", Html = "<p>B</p>" },
});
Console.WriteLine($"{batch.Total} {batch.Sent} {batch.Failed}");
 
// Dry-run a send without sending it.
ValidationResult check = await taifamail.Emails.ValidateAsync(sent: new SendEmail
{
    From = "hello@yourdomain.com",
    To = { "customer@example.com" },
    Subject = "Hello",
    Html = "<p>Hi</p>",
});
if (!check.CanSend)
{
    foreach (var issue in check.Issues)
        Console.WriteLine($"{issue.Field}: {issue.Error}");
}
 
// List recent emails, newest first. page is zero-based.
List<EmailRecord> emails = await taifamail.Emails.ListAsync(status: "delivered", page: 0, limit: 20);
 
// Fetch one email with its bodies and events.
EmailDetail detail = await taifamail.Emails.GetAsync(sent.Id);
 
// List delivery / open / click / bounce events for an email.
List<EmailEvent> events = await taifamail.Emails.EventsAsync(sent.Id);
 
// Re-send a bounced, rejected, or failed email as a new message.
SendEmailResult resent = await taifamail.Emails.RetryAsync(sent.Id);
 
// Search. q supports inline tokens: to:, from:, status:, domain:, tag:
List<EmailSearchHit> hits = await taifamail.Emails.SearchAsync(q: "welcome status:delivered", limit: 10);
 
// Poll for emails whose status changed at or after a timestamp (max 50).
List<EmailRecord> updated = await taifamail.Emails.UpdatesAsync(DateTimeOffset.UtcNow.AddMinutes(-1));

Scheduled emails:

List<ScheduledEmail> scheduled = await taifamail.Emails.ListScheduledAsync();
await taifamail.Emails.CancelScheduledAsync(scheduled[0].Id);
await taifamail.Emails.SendScheduledNowAsync(scheduled[0].Id);

Saved searches (named filter sets stored per user) are returned as raw JsonElement values:

var searches = await taifamail.Emails.GetSavedSearchesAsync();
await taifamail.Emails.SetSavedSearchesAsync(new object[]
{
    new { name = "Bounced today", query = "status:bounced" },
});

Domains

Accessed as client.Domains.

// List your sending domains and their verification status.
List<DomainRecord> domains = await taifamail.Domains.ListAsync();
 
// Register a new domain. Returns the DNS records to publish.
DomainDetail domain = await taifamail.Domains.CreateAsync("yourdomain.com");
 
// Fetch a domain with its DKIM selector and DNS records.
DomainDetail one = await taifamail.Domains.GetAsync(domain.Id);
 
// Re-check DNS and verify.
await taifamail.Domains.VerifyAsync(domain.Id);
 
// Live DNS health checks (DKIM, SPF, DMARC, return-path, MX).
DomainHealth health = await taifamail.Domains.HealthAsync(domain.Id);
 
// Diagnose configuration issues and get a health score.
DomainDiagnosis diagnosis = await taifamail.Domains.DiagnoseAsync(domain.Id);
 
// Rotate the DKIM key; returns the new record to publish.
DkimRotation rotation = await taifamail.Domains.RotateDkimAsync(domain.Id);
 
// Transfer the domain to another Taifa Mail account.
await taifamail.Domains.TransferAsync(domain.Id, "owner@other.com", note: "handoff");
 
// Check availability against public DNS, or whether it already exists in your account.
DomainAvailability availability = await taifamail.Domains.CheckAvailabilityAsync("newdomain.com");
DomainCheck exists = await taifamail.Domains.CheckAsync("yourdomain.com");
 
// Delete a domain.
await taifamail.Domains.DeleteAsync(domain.Id);

Contacts

Subscriber lists, their contacts, CSV imports, and templated bulk sends. Accessed as client.Contacts.

// Lists.
List<ContactList> lists = await taifamail.Contacts.ListListsAsync();
ContactList list = await taifamail.Contacts.CreateListAsync("Newsletter", description: "Monthly news");
ContactListDetail listDetail = await taifamail.Contacts.GetListAsync(list.Id, page: 0, limit: 50);
await taifamail.Contacts.UpdateListAsync(list.Id, name: "Monthly Newsletter");
await taifamail.Contacts.DeleteListAsync(list.Id);
 
// Contacts within a list.
Contact contact = await taifamail.Contacts.AddContactAsync(list.Id, "subscriber@example.com",
    name: "Subscriber", metadata: new { plan = "pro" });
await taifamail.Contacts.RemoveContactAsync(list.Id, contact.Id);

UploadCsvAsync takes the raw file bytes and a filename, sent as a single multipart field named file. The email column is auto-detected; other columns become contact metadata.

byte[] bytes = await File.ReadAllBytesAsync("contacts.csv");
CsvImportResult import = await taifamail.Contacts.UploadCsvAsync(list.Id, bytes, "contacts.csv");

BulkSendAsync mails a templated message to every contact in a list. Subject, html, and text may use {{email}}, {{name}}, and {{metadata_key}} placeholders:

BulkSendResult bulk = await taifamail.Contacts.BulkSendAsync(
    listId: list.Id,
    senderAddressId: "sender_123",
    subject: "Hello {{name}}",
    html: "<p>Hi {{name}}, here is the latest.</p>",
    tags: new[] { "newsletter" });

Suppressions

The do-not-send list. ListAsync returns a paginated Page<Suppression> envelope (Items, Total, Page, Limit). Accessed as client.Suppressions.

Page<Suppression> page = await taifamail.Suppressions.ListAsync(page: 0, limit: 50, search: "gmail.com");
Console.WriteLine($"{page.Items.Count} of {page.Total}");
 
await taifamail.Suppressions.AddAsync("unsubscribed@example.com", reason: "manual");
 
// Bulk import from a file (one email per line) as a single multipart field.
byte[] bytes = await File.ReadAllBytesAsync("suppressions.txt");
BulkSuppressionResult result = await taifamail.Suppressions.BulkUploadAsync(bytes, "suppressions.txt");
 
await taifamail.Suppressions.RemoveAsync("suppression_id");

Templates

Reusable email templates (Starter plan and up). The SDK's html / text arguments map to the wire fields html_body / text_body for you. Accessed as client.Templates.

List<Template> templates = await taifamail.Templates.ListAsync();
 
Template template = await taifamail.Templates.CreateAsync(
    name: "Receipt",
    subject: "Your receipt",
    html: "<h1>Thanks, {{name}}</h1>",
    text: "Thanks, {{name}}");
 
Template fetched = await taifamail.Templates.GetAsync(template.Id);
await taifamail.Templates.UpdateAsync(template.Id, subject: "Your order receipt");
Template copy = await taifamail.Templates.DuplicateAsync(template.Id);
await taifamail.Templates.DeleteAsync(template.Id);

A template's variables are derived server-side from the {{name}} placeholders in its bodies, so you do not pass them when creating or updating.

Webhooks

Accessed as client.Webhooks.

List<Webhook> webhooks = await taifamail.Webhooks.ListAsync();
 
// Create one. The signing secret is generated and returned in plaintext.
Webhook webhook = await taifamail.Webhooks.CreateAsync(
    url: "https://example.com/hooks/taifamail",
    events: new[] { "email.delivered", "email.bounced" });
Console.WriteLine(webhook.Secret);
 
await taifamail.Webhooks.UpdateAsync(webhook.Id, events: new[] { "email.opened" }, isActive: true);
 
// Queue a sample email.delivered delivery to test the endpoint.
await taifamail.Webhooks.TestAsync(webhook.Id);
 
// Inspect delivery attempts (paginated envelope).
Page<WebhookDelivery> deliveries = await taifamail.Webhooks.ListDeliveriesAsync(webhook.Id, page: 0, limit: 20);
WebhookDeliveryDetail delivery = await taifamail.Webhooks.GetDeliveryAsync(webhook.Id, deliveries.Items[0].Id);
 
await taifamail.Webhooks.DeleteAsync(webhook.Id);

Error handling

Every non-2xx response, and any transport failure that survives all retries, throws an TaifaMailException. Inspect Status and Code to branch on specific failures.

using TaifaMail.Sdk;
 
var taifamail = new TaifaMail("tfm_k_your_api_key");
 
try
{
    await taifamail.Emails.SendAsync(new SendEmail
    {
        From = "hello@yourdomain.com",
        To = { "customer@example.com" },
        Subject = "Hello",
        Html = "<p>Hi</p>",
    });
}
catch (TaifaMailException ex)
{
    Console.Error.WriteLine(ex.Status);  // HTTP status; 0 means a transport/network failure
    Console.Error.WriteLine(ex.Code);    // machine-readable code from the API body, when present
    Console.Error.WriteLine(ex.Message);
}

TaifaMailException.Status is 0 when no response was received (a transport or network failure that exhausted all retries). Code is populated from the API's detail body when present, and is null otherwise.

Configuration

  • Retries. Requests that return 429 or 5xx are retried with exponential backoff, honoring the Retry-After header when present. Control the total attempt count (including the first) with maxRetries (default 3). Multipart uploads (CSV and suppression imports) are not retried because they are not idempotent.
  • Custom HttpClient. Pass your own HttpClient, for example one from IHttpClientFactory, to share connection pooling and integrate with dependency injection. The SDK sets the Authorization header and base address on first use and does not dispose a client it did not create.
  • Custom base URL. Point the client at a staging or self-hosted host with the baseUrl parameter. A trailing slash is trimmed automatically.
var taifamail = new TaifaMail(
    apiKey: Environment.GetEnvironmentVariable("TAIFA_MAIL_API_KEY")!,
    baseUrl: "https://staging.govconnect.ke",
    maxRetries: 5);

Pass a CancellationToken to any method to cancel an in-flight request:

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await taifamail.Emails.SendAsync(message, cts.Token);

Next steps

On this page