Taifa MailTaifa Mail Docs
SDKs

Java SDK

Send email, manage domains, contacts, templates, and webhooks from the JVM with the official ke.govconnect:taifa-mail-sdk SDK.

The official Java SDK is published under the Maven coordinates ke.govconnect:taifa-mail-sdk on Maven Central. Source lives in the taifa-mail-sdks repository.

The SDK has a small dependency footprint (Jackson for JSON), uses the built-in java.net.http.HttpClient, and targets Java 11 and later.

Installation

Gradle (Kotlin DSL):

implementation("ke.govconnect:taifa-mail-sdk:0.1.0")

Maven:

<dependency>
  <groupId>ke.govconnect</groupId>
  <artifactId>mailer</artifactId>
  <version>0.1.0</version>
</dependency>

The SDK requires Java 11 or later. It uses the JDK's built-in java.net.http.HttpClient, so there is no third-party HTTP client to configure.

Client setup

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

import ke.govconnect.taifamail.TaifaMail;
 
TaifaMail taifamail = new TaifaMail("tfm_k_your_api_key");

An overloaded constructor lets you override the base URL and the retry count:

TaifaMail taifamail = new TaifaMail(
    "tfm_k_your_api_key",
    "https://govconnect.ke", // base URL (a trailing slash is trimmed)
    3                         // total attempts on 429 / 5xx, including the first
);
Constructor argumentTypeDefaultDescription
apiKeyString(required)Your API key. Starts with tfm_k_. Must not be blank.
baseUrlStringhttps://govconnect.keOverride the API base URL. A trailing slash is trimmed automatically.
maxRetriesint3Total attempts on 429 / 5xx, including the first. Clamped to at least 1.

Resources are exposed as accessor methods that share a single transport: taifamail.emails(), taifamail.domains(), taifamail.contacts(), taifamail.suppressions(), taifamail.templates(), and taifamail.webhooks().

Send an email

emails().send(...) queues a single message and returns its id and initial status. Build the message with SendEmail.builder().

import ke.govconnect.taifamail.TaifaMail;
import ke.govconnect.taifamail.SendEmail;
import ke.govconnect.taifamail.SendEmailResult;
 
TaifaMail taifamail = new TaifaMail("tfm_k_your_api_key");
 
SendEmailResult result = taifamail.emails().send(SendEmail.builder()
    .from("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.")
    .build());
 
System.out.println(result.id + " " + result.status);

The returned SendEmailResult has these fields:

FieldTypeDescription
idStringThe queued message id.
statusStringInitial status, typically queued.
messageIdStringThe SMTP Message-ID, wire field message_id. May be null.
rejectionReasonStringWhy the message was rejected, wire field rejection_reason. Null when accepted.

A few conveniences worth knowing:

  • The builder exposes a clean from(...) and maps it to the wire field from_ for you. You never write from_.
  • from, to, cc, bcc, and replyTo accept a bare email string, or an email plus a display name. to can be called more than once to add multiple recipients.
  • Provide html, text, or both.
  • sendAt(Instant) schedules the message for later delivery (Starter plan and up).
import java.time.Instant;
import java.time.temporal.ChronoUnit;
 
taifamail.emails().send(SendEmail.builder()
    .from("hello@yourdomain.com")
    .to("ada@example.com", "Ada")
    .to("grace@example.com")
    .cc("manager@example.com")
    .replyTo("support@yourdomain.com")
    .subject("Welcome aboard")
    .html("<p>Welcome!</p>")
    .tag("onboarding")
    .header("X-Campaign", "spring")
    .sendAt(Instant.now().plus(1, ChronoUnit.HOURS))
    .build());

TaifaMail also provides a shortcut taifamail.send(email) that delegates to emails().send(email).

Emails

import ke.govconnect.taifamail.*;
import java.time.Instant;
import java.util.List;
 
// Send a single email.
SendEmailResult sent = taifamail.emails().send(SendEmail.builder()
    .from("hello@yourdomain.com").to("a@example.com")
    .subject("Hi").html("<p>Hi</p>").build());
 
// Send a batch (bare array on the wire; Starter plan and up, capped by your plan).
BatchResult batch = taifamail.emails().sendBatch(List.of(
    SendEmail.builder().from("hello@yourdomain.com").to("a@example.com").subject("Hi").html("<p>1</p>").build(),
    SendEmail.builder().from("hello@yourdomain.com").to("b@example.com").subject("Hi").html("<p>2</p>").build()
));
 
// Dry-run a send without sending it.
ValidationResult check = taifamail.emails().validate(SendEmail.builder()
    .from("hello@yourdomain.com").to("a@example.com").subject("Hi").html("<p>Hi</p>").build());
 
// List recent emails (newest first). page is zero-based, limit is 1-100.
List<EmailRecord> emails = taifamail.emails().list("delivered", 0, 20);
 
// Fetch one email with bodies and events.
EmailDetail detail = taifamail.emails().get(sent.id);
 
// List delivery / open / click / bounce events for an email.
List<EmailEvent> events = taifamail.emails().events(sent.id);
 
// Re-send a bounced, rejected, or failed email as a new message.
SendEmailResult resent = taifamail.emails().retry(sent.id);
 
// Search. q supports inline tokens: to:, from:, status:, domain:, tag:
List<EmailSearchHit> hits = taifamail.emails().search("welcome status:delivered", null, null, 0, 10);
 
// Poll for emails whose status changed at or after a timestamp (max 50 rows).
List<EmailRecord> updated = taifamail.emails().updates(Instant.now().minusSeconds(60));

Scheduled emails:

List<ScheduledEmail> scheduled = taifamail.emails().listScheduled();
taifamail.emails().cancelScheduled(scheduled.get(0).id);
taifamail.emails().sendScheduledNow(scheduled.get(0).id);

Saved searches (named filter sets stored per user) are exposed as open maps:

import java.util.List;
import java.util.Map;
 
List<Map<String, Object>> searches = taifamail.emails().getSavedSearches();
taifamail.emails().setSavedSearches(List.of(
    Map.of("name", "Bounced today", "query", "status:bounced")
));

Domains

import ke.govconnect.taifamail.*;
import java.util.List;
 
// List your sending domains and their verification status.
List<DomainRecord> domains = taifamail.domains().list();
 
// Register a new domain. Returns the DNS records to publish.
DomainDetailRecord domain = taifamail.domains().create("yourdomain.com");
 
// Fetch a domain with its DKIM selector and DNS records.
DomainDetailRecord one = taifamail.domains().get(domain.id);
 
// Re-check DNS and verify.
taifamail.domains().verify(domain.id);
 
// Live DNS health checks (DKIM, SPF, DMARC, return-path, MX).
DomainHealth health = taifamail.domains().health(domain.id);
 
// Diagnose configuration issues and get a health score.
DomainDiagnosis diagnosis = taifamail.domains().diagnose(domain.id);
 
// Rotate the DKIM key; returns the new record to publish.
DkimRotation rotation = taifamail.domains().rotateDkim(domain.id);
 
// Transfer the domain to another Taifa Mail account (note may be null).
DomainTransfer transfer = taifamail.domains().transfer(domain.id, "owner@other.com", "handoff");
 
// Check availability against public DNS, or whether it already exists in your account.
DomainAvailability availability = taifamail.domains().checkAvailability("newdomain.com");
DomainCheck exists = taifamail.domains().check("yourdomain.com");
 
// Delete a domain.
taifamail.domains().delete(domain.id);

Contacts

Subscriber lists, their contacts, CSV imports, and templated bulk sends.

import ke.govconnect.taifamail.*;
import java.util.List;
import java.util.Map;
 
// Lists.
List<ContactList> lists = taifamail.contacts().listLists();
ContactList list = taifamail.contacts().createList(ContactListParams.builder()
    .name("Newsletter").description("Monthly news").build());
ContactListDetail listDetail = taifamail.contacts().getList(list.id, 0, 50);
taifamail.contacts().updateList(list.id, ContactListParams.builder().name("Monthly Newsletter").build());
taifamail.contacts().deleteList(list.id);
 
// Contacts within a list.
Contact contact = taifamail.contacts().addContact(list.id, ContactParams.builder()
    .email("subscriber@example.com")
    .name("Subscriber")
    .metadata(Map.of("plan", "pro"))
    .build());
taifamail.contacts().removeContact(list.id, contact.id);

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

import java.nio.file.Files;
import java.nio.file.Path;
 
byte[] bytes = Files.readAllBytes(Path.of("contacts.csv"));
CsvImportResult importResult = taifamail.contacts().uploadCsv(list.id, bytes, "contacts.csv");

bulkSend mails a templated message to every contact in a list. Subject, html, and text may use {{email}}, {{name}}, and {{metadata_key}} placeholders. The contact_list_id wire field is set from the list id automatically.

BulkSendResult result = taifamail.contacts().bulkSend(list.id, BulkSendParams.builder()
    .senderAddressId("sender_123")
    .subject("Hello {{name}}")
    .html("<p>Hi {{name}}, here is the latest.</p>")
    .tag("newsletter")
    .build());

Suppressions

The do-not-send list. list returns a paginated Page envelope (items, total, page, limit).

import ke.govconnect.taifamail.*;
 
Page<Suppression> page = taifamail.suppressions().list(0, 50, "gmail.com");
 
// Suppress an address. reason defaults to "manual" when null.
taifamail.suppressions().add("unsubscribed@example.com", "manual");
 
// Bulk import from a file (one email per line) as a single multipart field.
byte[] bytes = java.nio.file.Files.readAllBytes(java.nio.file.Path.of("suppressions.txt"));
BulkSuppressionResult bulk = taifamail.suppressions().bulkUpload(bytes, "suppressions.txt");
 
taifamail.suppressions().remove("suppression_id");

Templates

Reusable email templates (Starter plan and up). html maps to the wire field html_body and text to text_body for you.

import ke.govconnect.taifamail.*;
import java.util.List;
 
List<Template> templates = taifamail.templates().list();
 
Template template = taifamail.templates().create(TemplateParams.builder()
    .name("Receipt")
    .subject("Your receipt")
    .html("<h1>Thanks, {{name}}</h1>")
    .text("Thanks, {{name}}")
    .build());
 
Template fetched = taifamail.templates().get(template.id);
taifamail.templates().update(template.id, TemplateParams.builder().subject("Your order receipt").build());
Template copy = taifamail.templates().duplicate(template.id);
taifamail.templates().delete(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

import ke.govconnect.taifamail.*;
import java.util.List;
 
List<Webhook> webhooks = taifamail.webhooks().list();
 
// Create one. The signing secret is generated and returned in plaintext.
Webhook webhook = taifamail.webhooks().create(WebhookParams.builder()
    .url("https://example.com/hooks/taifamail")
    .event("email.delivered")
    .event("email.bounced")
    .build());
 
taifamail.webhooks().update(webhook.id, WebhookParams.builder()
    .events(List.of("email.opened"))
    .isActive(true)
    .build());
 
// Queue a sample email.delivered delivery to test the endpoint.
WebhookTestResult test = taifamail.webhooks().test(webhook.id);
 
// Inspect delivery attempts (paginated envelope).
Page<WebhookDelivery> deliveries = taifamail.webhooks().listDeliveries(webhook.id, 0, 20, null);
WebhookDeliveryDetail delivery = taifamail.webhooks().getDelivery(webhook.id, deliveries.items.get(0).id);
 
taifamail.webhooks().delete(webhook.id);

Error handling

Every non-2xx response, and any transport failure that survives all retries, throws an TaifaMailException (an unchecked RuntimeException). Inspect getStatus() and getCode() to branch on specific failures.

import ke.govconnect.taifamail.TaifaMailException;
import ke.govconnect.taifamail.SendEmail;
 
try {
    taifamail.emails().send(SendEmail.builder()
        .from("hello@yourdomain.com")
        .to("customer@example.com")
        .subject("Hello")
        .html("<p>Hi</p>")
        .build());
} catch (TaifaMailException e) {
    System.err.println(e.getStatus());  // HTTP status; 0 means a transport/network failure
    System.err.println(e.getCode());    // machine-readable code from the API body, when present
    System.err.println(e.getMessage());
}

Configuration

  • Retries. Requests that return 429 or 5xx are retried with exponential backoff, honouring the Retry-After header when present. Control the total attempt count (including the first) with the maxRetries constructor argument (default 3). Multipart uploads (uploadCsv, bulkUpload) are not retried because they are not idempotent.
  • Timeout. Each request has a 30-second timeout and a 15-second connect timeout. A timeout is treated as a transport failure and is retried while attempts remain.
  • Custom base URL. Point the client at a staging or self-hosted host with the second constructor argument. A trailing slash is trimmed automatically.
TaifaMail taifamail = new TaifaMail(
    System.getenv("TAIFA_MAIL_API_KEY"),
    "https://staging.govconnect.ke",
    5
);

Next steps

On this page