Taifa MailTaifa Mail Docs
SDKs

Ruby SDK

Install and use the official Taifa Mail Ruby SDK to send email, manage domains, contacts, suppressions, templates, and webhooks.

The Taifa Mail Ruby SDK is a thin, idiomatic wrapper over the Mailer API. It ships as the taifa-mail gem on RubyGems and exposes one client object with a resource accessor per API group.

Source: github.com/GovConnectKenya/taifa-mail-sdks/tree/main/packages/ruby


Installation

The gem requires Ruby 3.0 or newer and has zero runtime dependencies (it uses net/http from the standard library).

Install it directly:

gem install taifa-mail

Or add it to your Gemfile:

gem "taifa-mail"

Then run bundle install.


Client setup

Construct a client with your API key. The key starts with tfm_k_ and is passed on every request as a bearer token.

require "taifamail/sdk"
 
client = Taifa Mail::Mailer::Client.new(api_key: ENV.fetch("TAIFA_MAIL_API_KEY"))

Never hard-code your API key. Load it from an environment variable or a secrets manager.

The constructor accepts these options:

OptionTypeDefaultDescription
api_keyString(required)Your API key. Must start with tfm_k_.
base_urlStringhttps://govconnect.keAPI base URL. Override for a self-hosted or staging environment.
max_retriesInteger3Retry attempts for 429 and 5xx responses, with exponential backoff that honours Retry-After.
timeoutNumeric30Per-request open and read timeout, in seconds.
client = Taifa Mail::Mailer::Client.new(
  api_key: ENV.fetch("TAIFA_MAIL_API_KEY"),
  base_url: "https://govconnect.ke",
  max_retries: 5,
  timeout: 60
)

The client exposes six resources: client.emails, client.domains, client.contacts, client.suppressions, client.templates, and client.webhooks.


Send an email

client.emails.send accepts keyword arguments (or a Hash). You pass a clean from, which the SDK maps to the API's from_ wire field. Anywhere an address is expected you may pass a bare string (treated as { email: ... }) or a Hash with email and an optional name.

response = client.emails.send(
  from: { email: "hello@yourdomain.com", name: "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."
)
 
puts response[:id]      # "a1b2c3d4-5678-90ab-cdef-1234567890ab"
puts response[:status]  # "queued"

Responses are plain Ruby Hashes with symbol keys. A send returns:

{
  id: "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  status: "queued",
  message_id: "<uuid@govconnect.ke>",
  rejection_reason: nil
}

to, cc, and bcc each accept a single address or an array of addresses. Additional supported keys include reply_to, headers, tags, send_at (a Time or ISO 8601 string, for scheduled delivery), and attachments.

client.emails.send(
  from: "hello@yourdomain.com",
  to: [{ email: "jane@example.com", name: "Jane Doe" }, "team@example.com"],
  cc: "manager@example.com",
  subject: "Weekly report",
  html: "<p>See attached.</p>",
  tags: ["reports", "weekly"],
  reply_to: "support@yourdomain.com",
  send_at: Time.now + 3600,
  attachments: [
    {
      filename: "report.pdf",
      content_base64: Base64.strict_encode64(File.binread("report.pdf")),
      content_type: "application/pdf"
    }
  ]
)

Emails

# Send a batch (one call, returns a per-message result set)
client.emails.send_batch([
  { from: "hello@yourdomain.com", to: "a@example.com", subject: "Hi", html: "<p>1</p>" },
  { from: "hello@yourdomain.com", to: "b@example.com", subject: "Hi", html: "<p>2</p>" }
])
 
# Dry-run validation (never sends)
result = client.emails.validate(
  from: "hello@yourdomain.com",
  to: "customer@example.com",
  subject: "Test",
  html: "<p>Test</p>"
)
result[:can_send]  # => true
 
# List, get, events
client.emails.list(status: "delivered", page: 0, limit: 20)
client.emails.get("a1b2c3d4-...")
client.emails.events("a1b2c3d4-...")
 
# Retry a bounced/rejected/failed email
client.emails.retry("a1b2c3d4-...")
 
# Search (q supports to:, from:, status:, domain:, tag: tokens)
client.emails.search(q: "welcome status:delivered", page: 0, limit: 20)
 
# Scheduled emails
client.emails.list_scheduled
client.emails.cancel_scheduled("b2c3d4e5-...")
client.emails.send_scheduled_now("b2c3d4e5-...")
 
# Poll for status changes since a Time or ISO 8601 string (max 50 rows)
client.emails.updates(Time.now - 300)
 
# Saved searches
client.emails.get_saved_searches
client.emails.set_saved_searches([{ name: "Bounces", query: "status:bounced" }])

Domains

client.domains.list
client.domains.create("yourdomain.com")          # returns DNS records to publish
client.domains.get("dom_id")
client.domains.delete("dom_id")
client.domains.verify("dom_id")                   # re-check DNS and verify
 
# Health and diagnostics
client.domains.health("dom_id")
client.domains.diagnose("dom_id")
client.domains.mx_status("dom_id")
client.domains.published_records("dom_id")
 
# DKIM rotation and transfer
client.domains.rotate_dkim("dom_id")
client.domains.transfer("dom_id", target_email: "owner@otheraccount.com", note: "Handover")
 
# Availability checks
client.domains.check_availability("yourdomain.com")
client.domains.check("yourdomain.com")

Contacts

# Lists
client.contacts.list_lists
client.contacts.create_list(name: "Newsletter", description: "Monthly updates")
client.contacts.get_list("list_id", page: 0, limit: 50)
client.contacts.update_list("list_id", name: "Renamed")
client.contacts.delete_list("list_id")
 
# Contacts within a list
client.contacts.add_contact("list_id", email: "jane@example.com", name: "Jane", metadata: { plan: "pro" })
client.contacts.remove_contact("list_id", "contact_id")
 
# CSV import (file may be raw bytes or a path; header row required)
client.contacts.upload_csv("list_id", "contacts.csv")
 
# Templated bulk send to every contact in the list
client.contacts.bulk_send(
  "list_id",
  sender_address_id: "sender_id",
  subject: "Hello {{name}}",
  html: "<p>Hi {{name}}, welcome aboard.</p>"
)

Bulk send subject and bodies support {{email}}, {{name}}, and {{metadata_key}} placeholders.


Suppressions

The suppression list endpoints return a paginated envelope ({ items:, total:, page:, limit: }). The email argument maps to the API's email_address wire field.

page = client.suppressions.list(page: 0, limit: 50, search: "example.com")
page[:items]
 
client.suppressions.add(email: "bounced@example.com", reason: "manual")
 
# Bulk import (one email per line; raw bytes or a path)
client.suppressions.bulk_upload("suppressions.txt")
 
client.suppressions.remove("suppression_id")

Templates

Reusable templates are available on the Starter plan and above. For this resource only, html maps to html_body and text maps to text_body.

client.templates.list
client.templates.create(name: "Welcome", subject: "Welcome {{name}}", html: "<p>Hi {{name}}</p>")
client.templates.get("tpl_id")
client.templates.update("tpl_id", subject: "Welcome aboard {{name}}")
client.templates.delete("tpl_id")
client.templates.duplicate("tpl_id")

The variables field is derived server-side from {{name}} placeholders, so you do not pass it.


Webhooks

The deliveries listing returns a paginated envelope ({ items:, total:, page:, limit: }).

client.webhooks.list
client.webhooks.create(url: "https://example.com/hooks", events: ["email.delivered"])
client.webhooks.update("wh_id", events: ["email.delivered", "email.bounced"], is_active: true)
client.webhooks.delete("wh_id")
 
# Queue a sample email.delivered delivery to test the endpoint
client.webhooks.test("wh_id")
 
# Inspect deliveries
client.webhooks.list_deliveries("wh_id", page: 0, limit: 20, status: "failed")
client.webhooks.get_delivery("wh_id", "delivery_id")

Error handling

Any non-2xx response (and any transport failure that survives every retry) raises Taifa Mail::Mailer::Error. Inspect #status, #code, and #message to branch on specific failures. A #status of 0 indicates a network or transport failure with no HTTP response, and #detail holds the raw parsed response body for debugging.

begin
  client.emails.send(
    from: "hello@yourdomain.com",
    to: "customer@example.com",
    subject: "Your receipt",
    html: "<p>Thanks.</p>"
  )
rescue Taifa Mail::Mailer::Error => e
  warn "Send failed (#{e.status}): #{e.message}"
  warn "Code: #{e.code}" if e.code
end
ReaderTypeDescription
#statusIntegerHTTP status code. 0 for transport or network failures.
#codeString, nilMachine-readable error code from the API body, when present.
#messageStringHuman-readable error message.
#detailObject, nilThe raw parsed response body.

The SDK retries 429 and 5xx responses automatically (up to max_retries), honouring Retry-After. Client errors in the 4xx range are never retried and surface immediately.


Configuration

All configuration is passed to the constructor. There is no global or file-based config.

client = Taifa Mail::Mailer::Client.new(
  api_key: ENV.fetch("TAIFA_MAIL_API_KEY"),  # required, starts with tfm_k_
  base_url: ENV.fetch("TAIFA_MAIL_BASE_URL", "https://govconnect.ke"),
  max_retries: 3,
  timeout: 30
)
  • base_url is useful for pointing the client at a staging environment.
  • max_retries and timeout tune the network behaviour for your workload.
  • Multipart uploads (upload_csv, bulk_upload) are not retried, because uploads are not idempotent.

Next steps

On this page