SDKs & CLI

Go SDK

The official Go client for MailAfrica. Standard-library only, typed requests and responses, automatic JWT refresh, and observability hooks.


Overview

The Go SDK is an open-source, standard-library-only client for the MailAfrica API. It wraps the JSON HTTP API with typed request/response structs, unwraps the response envelope, sets authentication headers, normalizes errors into a single *APIError, and can refresh JWTs automatically. No external dependencies — just the Go standard library.

Source: github.com/MailAfrica/go-sdk · Module: github.com/mailafrica/go-sdk · Go 1.25+ · MIT license.

Install

bash
go get github.com/mailafrica/go-sdk

Quickstart

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/mailafrica/go-sdk"
)

func main() {
	ctx := context.Background()

	client := mailafrica.New(mailafrica.Config{
		BaseURL: "https://api.mailafrica.online",
		APIKey:  os.Getenv("MAIL_API_KEY"),
	})

	msg, err := client.SendEmail(ctx, mailafrica.SendEmailRequest{
		To:       []string{"recipient@example.com"},
		Subject:  "Hello from MailAfrica",
		HTMLBody: "<p>Hello!</p>",
		TextBody: "Hello!",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("sent message ID:", msg.ID)
}
Use your MAIL_... API key for server-side code — never ship it in client-side apps. The SDK holds the key in memory and sends it as X-API-Key on every request.

Configuration

Create a client with mailafrica.New(mailafrica.Config{...}). Every field is optional — sensible defaults apply:

Config fieldDefaultPurpose
BaseURLhttps://api.mailafrica.onlineAPI base URL.
APIKeySends X-API-Key on every request (preferred).
JWTSends Authorization: Bearer; used with TokenRefresher.
Timeout30sHTTP client timeout.
UserAgentmailafrica-go/0.1.0User-Agent header.
TokenRefresherfunc(ctx) (string, error) called on 401 to mint a new JWT.
HooksObservability callbacks — see Hooks.

Authentication

Set one credential in Config. API keys are recommended for servers and CLIs; JWTs suit browser/SPA flows where you already hold a session token.

Config fieldHeader sentWhen to use
APIKeyX-API-Key: <key>Preferred for server-side and CLI usage.
JWTAuthorization: Bearer <jwt>Browser/SPA flows or an existing JWT.

Automatic JWT refresh

Pass a TokenRefresher. On any 401 the SDK calls it, stores the new token thread-safely, and retries the request once. The SDK never stores or handles refresh tokens — that logic stays in your code.

go
client := mailafrica.New(mailafrica.Config{
	JWT: initialToken,
	TokenRefresher: func(ctx context.Context) (string, error) {
		return myRefreshLogic(ctx) // returns a fresh JWT
	},
})

Method reference

All 69 client methods, grouped by service. Every method takes ctx context.Context as its first argument and returns an error as its last value. Examples assume client was created as in the Quickstart.

The SDK uses pointer helpers (strPtr, int64Ptr, boolPtr, intPtr, timePtr) in these snippets to build *T values for fields that are optional or emitted conditionally.

Account & auth

Register, log in, and manage your profile and verification status. See Authentication for the underlying endpoints.

go
// Register a new account (email or phone_number; password is required)
resp, err := client.Register(ctx, mailafrica.RegisterRequest{
	Email:       strPtr("user@example.com"),
	Phone:       strPtr("+255712345678"),
	Password:    "secure-password",
	Name:        "Jane Doe",
	CompanyName: "Acme Corp",
})
authToken    := resp.Token        // JWT access token
refreshToken := resp.RefreshToken // store it; the SDK never does

// Log in with an email or phone identifier
resp, err = client.Login(ctx, mailafrica.LoginRequest{
	Identifier: "user@example.com",
	Password:   "secure-password",
})

// Log in with a Google ID token
resp, err = client.GoogleLogin(ctx, "google-id-token")

// Exchange a refresh token for a fresh JWT
resp, err = client.Refresh(ctx, refreshToken)

// Fetch the current authenticated user
user, err := client.Me(ctx)

// Update your profile
user, err = client.UpdateMe(ctx, mailafrica.UpdateProfileRequest{
	Name:        strPtr("Jane D. Doe"),
	CompanyName: strPtr("Acme Inc"),
})
go
// Add or replace your email — sends a verification link
user, err := client.SetEmail(ctx, "new@example.com")

// Confirm the email using the token from the verification link
err = client.VerifyEmail(ctx, "verification-token")

// Re-send the verification email
err = client.ResendEmailVerification(ctx)

// Add or replace your phone — sends a 6-digit OTP
user, err = client.SetPhone(ctx, "+255712345678")

// Confirm the phone using the OTP
err = client.VerifyPhone(ctx, "123456")

// Re-send the OTP
err = client.ResendPhoneOTP(ctx)

Inbound addresses & messages

Create inbound addresses and read the mail delivered to them. For real-time delivery, register a webhook instead of polling.

go
// Create an inbound address
addr, err := client.CreateAddress(ctx, mailafrica.CreateAddressRequest{
	LocalPart: "hello",
	Label:     strPtr("Support inbox"),
})

// List all inbound addresses
addresses, err := client.ListAddresses(ctx)

// List messages with optional filters; returns pagination metadata
msgs, pagination, err := client.ListMessages(ctx, mailafrica.MessageListOpts{
	ListOpts:  mailafrica.ListOpts{Page: 1, PerPage: 25},
	AddressID: int64Ptr(addr.ID),
	Unread:    boolPtr(true),
})
fmt.Println("unread:", pagination.Total)

// Fetch one message by ID
msg, err := client.GetMessage(ctx, msgs[0].ID)

// Mark a message as read
err = client.MarkMessageRead(ctx, msg.ID)

// Delete an inbound address — it stops receiving mail
err = client.DeleteAddress(ctx, addr.ID)

Inbound domains

Receive mail on your own verified domain in addition to mailafrica.online. See Receiving domains.

go
// Add a receiving domain; returns the DNS records to publish
dom, err := client.CreateInboundDomain(ctx, "inbound.example.com")
fmt.Println(dom.VerificationRecord.Type, dom.VerificationRecord.Host, dom.VerificationRecord.Value)

// List receiving domains
domains, err := client.ListInboundDomains(ctx)

// Verify once DNS propagates
err = client.VerifyInboundDomain(ctx, dom.ID)

// Delete a receiving domain
err = client.DeleteInboundDomain(ctx, dom.ID)

Outbound email

Send transactional email from the platform sender or your own verified domain. See Outbound email.

go
// Send a single email
msg, err := client.SendEmail(ctx, mailafrica.SendEmailRequest{
	To:       []string{"customer@example.com"},
	Cc:       []string{"billing@example.com"},
	Subject:  "Your receipt",
	HTMLBody: "<p>Thanks for your order #482.</p>",
	TextBody: "Thanks for your order #482.",
	// Send from your own verified sending domain:
	FromDomainID: int64Ptr(7),
	FromAddress:  strPtr("hello@example.com"),
})

// Send to many recipients in one call
result, err := client.BatchSend(ctx, mailafrica.BatchSendRequest{
	To:       []string{"a@example.com", "b@example.com"},
	Subject:  "Batch update",
	HTMLBody: "<p>Hello!</p>",
	TextBody: "Hello!",
})
fmt.Println("sent:", result.Sent, "of", result.Total)
go
// List sent emails (paginated)
emails, pagination, err := client.ListSentEmails(ctx, mailafrica.ListOpts{
	Page:    1,
	PerPage: 25,
})

// Fetch a sent email with per-recipient delivery statuses
detail, err := client.GetSentEmail(ctx, emails[0].ID)
for _, r := range detail.Recipients {
	fmt.Println(r.Recipient, r.Status)
}
  • Charged per recipient from your TZS wallet and refunded on provider failure.
  • Status is sent | failedsent means accepted upstream; delivery/bounce tracking is not available.

Templates

Store reusable message bodies with {{variable}} placeholders. See Templates.

go
// Create a template with {{variable}} placeholders
tpl, err := client.CreateTemplate(ctx, mailafrica.TemplateRequest{
	Name:     "Welcome",
	Subject:  "Welcome {{name}}!",
	HTMLBody: "<p>Hi {{name}},</p>",
	TextBody: "Hi {{name}},",
})

// List / get / update / delete templates
tpls, err := client.ListTemplates(ctx)
tpl, err = client.GetTemplate(ctx, tpl.ID)
tpl, err = client.UpdateTemplate(ctx, tpl.ID, mailafrica.TemplateRequest{
	Name:     "Welcome v2",
	Subject:  "Welcome!",
	HTMLBody: "<p>Welcome!</p>",
})
err = client.DeleteTemplate(ctx, tpl.ID)

// Send with a template plus variables
msg, err := client.SendEmail(ctx, mailafrica.SendEmailRequest{
	To:         []string{"customer@example.com"},
	TemplateID: int64Ptr(tpl.ID),
	Variables:  map[string]string{"name": "Jane"},
})

Sending domains

Add a sending domain to get the DKIM, SPF, and DMARC records you publish, then verify once DNS propagates. See Sending domains.

go
// Add a sending domain; DNSRecords carries what to publish
resp, err := client.AddSendingDomain(ctx, mailafrica.AddSendingDomainRequest{
	Domain:        "example.com",
	FromLocalPart: "hello",
})
fmt.Println("DKIM host:", resp.DNSRecords.DKIM.Host)
fmt.Println("DKIM TXT:",  resp.DNSRecords.DKIM.Value)

// List sending domains
domains, err := client.ListSendingDomains(ctx)

// Verify a domain once DNS propagates
err = client.VerifySendingDomain(ctx, resp.Domain.ID)

// Delete a sending domain
err = client.DeleteSendingDomain(ctx, resp.Domain.ID)

Sender addresses

go
// Create a sender address on a domain (e.g. hello@example.com)
addr, err := client.CreateSenderAddress(ctx, domainID, "hello")

// List sender addresses
addrs, err := client.ListSenderAddresses(ctx)

// Delete a sender address
err = client.DeleteSenderAddress(ctx, addr.ID)

Webhooks

Receive inbound-mail notifications over HTTP, signed so you can verify them. See Webhooks.

go
// Create a webhook; Secret is auto-generated if omitted
wh, err := client.CreateWebhook(ctx, mailafrica.CreateWebhookRequest{
	AddressID: addr.ID,
	URL:       "https://example.com/hooks/inbound",
	Secret:    "whsec_...",
})
// wh.Secret is returned only on creation — store it securely

// List webhooks for an address
webhooks, err := client.ListWebhooks(ctx, addr.ID)

// Inspect delivery attempts (status, retries, last_error)
deliveries, err := client.ListWebhookDeliveries(ctx, wh.ID)

// Send a test ping
err = client.TestWebhook(ctx, wh.ID)

// Manually trigger the webhook
err = client.TriggerWebhook(ctx, wh.ID)

// Delete a webhook
err = client.DeleteWebhook(ctx, wh.ID)

Sandbox

Test the whole flow end-to-end with disposable credentials and a throwaway inbox. See Sandbox.

go
// Create a sandbox OAuth credential with scopes
cred, err := client.CreateSandboxCredential(ctx, mailafrica.CreateCredentialRequest{
	Scopes: strPtr("send,read"),
})

// List sandbox credentials
creds, err := client.ListSandboxCredentials(ctx)

// Revoke a credential
err = client.RevokeSandboxCredential(ctx, cred.ID)

// SMTP credentials for sending test mail into the sandbox
smtp, err := client.GetSMTPSandboxCredentials(ctx)
// smtp.Password is shown only on first generation / regeneration

// Regenerate the SMTP password
smtp, err = client.RegenerateSMTPSandboxPassword(ctx)

// Read sandbox mail (paginated)
messages, pagination, err := client.ListSandboxMessages(ctx, mailafrica.ListOpts{PerPage: 25})

// Fetch one sandbox message
smsg, err := client.GetSandboxMessage(ctx, messages[0].ID)

// Wipe the sandbox inbox
err = client.ClearSandboxMessages(ctx)

Billing

Read your wallet balance and start top-ups. See Balance.

go
// Read the wallet balance
balance, err := client.GetBalance(ctx)
fmt.Println("balance (TZS):", balance.BalanceTZS)

// Start a card / other top-up
topup, err := client.InitiateTopup(ctx, 10000)

// Start a mobile-money (phone) top-up
topup, err = client.InitiatePhoneTopup(ctx, 10000)
// topup.CheckoutURL / topup.PaymentLinkURL / topup.ProviderReference guide the payment

SMS notifications

Forward a short summary of inbound mail to a phone number. See SMS notifications.

go
// Create a notification rule
notif, err := client.CreateSMSNotification(ctx, mailafrica.CreateSMSNotificationRequest{
	AddressID:   addr.ID,
	PhoneNumber: "+255712345678",
	APIKey:      "SENDAFRICA_...",
})
// notif.APIKey is shown only once — store it securely

// List notification rules for an address
notifs, err := client.ListSMSNotifications(ctx, addr.ID)

// Inspect delivery attempts
deliveries, err := client.ListSMSDeliveries(ctx, notif.ID)

// Revoke a notification rule
err = client.RevokeSMSNotification(ctx, notif.ID)

Compliance

Manage your PDPC compliance profile and export audit data. See Compliance.

go
// Get the compliance profile
profile, err := client.GetComplianceProfile(ctx)

// Update PDPC registration, retention, etc.
profile, err = client.UpdateComplianceProfile(ctx, mailafrica.UpdateComplianceProfileRequest{
	PDPCRegistered:        boolPtr(true),
	PDPCCertificateNumber: strPtr("PDPC/2024/001"),
	DefaultRetentionDays:  intPtr(30),
})

// Export a compliance audit summary
export, err := client.GetAuditExport(ctx)
fmt.Println("messages:", export.MessageCount, "addresses:", export.AddressCount)

AI auto-reply agent

Per-address AI answers to inbound mail in off, draft, or auto mode. See AI auto-reply and Integrate with AI Assistants.

go
// List agent configs across addresses
configs, err := client.ListAgentConfigs(ctx)

// Get the config for one address
config, err := client.GetAgentConfig(ctx, addr.ID)

// Set mode, persona, and where replies come from
config, err = client.UpdateAgentConfig(ctx, addr.ID, mailafrica.UpdateAgentConfigRequest{
	Mode:              "draft", // "off" | "draft" | "auto"
	Enabled:           boolPtr(true),
	Persona:           strPtr("You are a helpful support agent."),
	ReplyFromDomainID: int64Ptr(7),
	ReplyFromAddress:  strPtr("support@example.com"),
})

// Generate a one-off reply draft (respects mode; never sends)
draft, err := client.GenerateAgentDraft(ctx, addr.ID, mailafrica.AgentDraftRequest{
	Subject: "Re: Order #482",
	Body:    "Where is my order?",
})
fmt.Println(draft.Draft)

API keys

Create and manage MAIL_... API keys for your integrations. See API keys.

go
// Create an API key; the plaintext key is shown only once
keyResp, err := client.CreateAPIKey(ctx, mailafrica.CreateAPIKeyRequest{
	Name:      "CLI Key",
	Scopes:    "send,read",
	ExpiresAt: timePtr(time.Now().Add(365 * 24 * time.Hour)), // optional
})
fmt.Println("store this once:", keyResp.Key)

// List API keys
keys, err := client.ListAPIKeys(ctx)

// Revoke an API key
err = client.RevokeAPIKey(ctx, keyResp.APIKey.ID)

Error handling

Every failure returns a *mailafrica.APIError with the backend code, message, HTTP status, and request_id. Inspect it with errors.As, or use the sentinel helpers.

go
_, err := client.SendEmail(ctx, req)
if err != nil {
	var apiErr *mailafrica.APIError
	if errors.As(err, &apiErr) {
		fmt.Println("code:", apiErr.Code)
		fmt.Println("status:", apiErr.HTTPStatus)
		fmt.Println("request_id:", apiErr.RequestID)
	}

	if mailafrica.IsInsufficientBalance(err) {
		// top up, then retry
	}
	if mailafrica.IsRateLimited(err) {
		// back off exponentially and retry
	}
}
HelperBackend code
IsInsufficientBalance(err)INSUFFICIENT_BALANCE
IsRateLimited(err)RATE_LIMITED
IsNotVerified(err)NOT_VERIFIED
IsAccountDisabled(err)ACCOUNT_DISABLED
IsNotFound(err)NOT_FOUND

Pagination

Collection methods return the slice plus a *Pagination (Page, PerPage, Total, TotalPages). Defaults to page 1 at 25 per page, capped at 100.

go
emails, pagination, err := client.ListSentEmails(ctx, mailafrica.ListOpts{
	Page:    1,
	PerPage: 25,
})
fmt.Println("total:", pagination.Total, "pages:", pagination.TotalPages)

Observability hooks

Wire Hooks to observe requests, responses, and errors without pulling in a logging framework.

go
client := mailafrica.New(mailafrica.Config{
	BaseURL: "https://api.mailafrica.online",
	APIKey:  os.Getenv("MAIL_API_KEY"),
	Hooks: &mailafrica.Hooks{
		OnRequest: func(req *http.Request) {
			log.Println("request:", req.Method, req.URL)
		},
		OnResponse: func(resp *http.Response, d time.Duration) {
			log.Println("response:", resp.StatusCode, d)
		},
		OnError: func(err error) {
			log.Println("error:", err)
		},
	},
})

Full method reference

The SDK covers every user-facing MailAfrica endpoint — nothing is missing except admin routes. Every method is listed below with the REST endpoint it calls:

ServiceMethodREST endpoint
AuthRegister(ctx, req)POST /api/auth/register
AuthLogin(ctx, req)POST /api/auth/login
AuthGoogleLogin(ctx, idToken)POST /api/auth/google
AuthRefresh(ctx, refreshToken)POST /api/auth/refresh
AuthMe(ctx)GET /api/auth/me
AuthUpdateMe(ctx, req)PATCH /api/auth/me
AuthSetEmail(ctx, email)POST /api/auth/email
AuthVerifyEmail(ctx, token)POST /api/auth/email/verify
AuthResendEmailVerification(ctx)POST /api/auth/email/resend
AuthSetPhone(ctx, phone)POST /api/auth/phone
AuthVerifyPhone(ctx, code)POST /api/auth/phone/verify
AuthResendPhoneOTP(ctx)POST /api/auth/phone/resend
InboundCreateAddress(ctx, req)POST /api/inbound/addresses
InboundListAddresses(ctx)GET /api/inbound/addresses
InboundDeleteAddress(ctx, id)DELETE /api/inbound/addresses/{id}
InboundListMessages(ctx, opts)GET /api/inbound/messages
InboundGetMessage(ctx, id)GET /api/inbound/messages/{id}
InboundMarkMessageRead(ctx, id)PATCH /api/inbound/messages/{id}/read
InboundCreateInboundDomain(ctx, domain)POST /api/inbound/domains
InboundListInboundDomains(ctx)GET /api/inbound/domains
InboundVerifyInboundDomain(ctx, id)POST /api/inbound/domains/{id}/verify
InboundDeleteInboundDomain(ctx, id)DELETE /api/inbound/domains/{id}
OutboundSendEmail(ctx, req)POST /api/outbound/emails
OutboundBatchSend(ctx, req)POST /api/outbound/emails/batch
OutboundListSentEmails(ctx, opts)GET /api/outbound/emails
OutboundGetSentEmail(ctx, id)GET /api/outbound/emails/{id}
OutboundCreateTemplate(ctx, req)POST /api/outbound/templates
OutboundListTemplates(ctx)GET /api/outbound/templates
OutboundGetTemplate(ctx, id)GET /api/outbound/templates/{id}
OutboundUpdateTemplate(ctx, id, req)PATCH /api/outbound/templates/{id}
OutboundDeleteTemplate(ctx, id)DELETE /api/outbound/templates/{id}
Sending domainsAddSendingDomain(ctx, req)POST /api/domains
Sending domainsListSendingDomains(ctx)GET /api/domains
Sending domainsVerifySendingDomain(ctx, id)POST /api/domains/{id}/verify
Sending domainsDeleteSendingDomain(ctx, id)DELETE /api/domains/{id}
Sender addressesCreateSenderAddress(ctx, domainID, localPart)POST /api/domains/{id}/senders
Sender addressesListSenderAddresses(ctx)GET /api/domains/senders
Sender addressesDeleteSenderAddress(ctx, id)DELETE /api/domains/senders/{id}
WebhooksCreateWebhook(ctx, req)POST /api/webhook/webhooks
WebhooksListWebhooks(ctx, addressID)GET /api/webhook/webhooks
WebhooksDeleteWebhook(ctx, id)DELETE /api/webhook/webhooks/{id}
WebhooksListWebhookDeliveries(ctx, webhookID)GET /api/webhook/webhooks/{id}/deliveries
WebhooksTestWebhook(ctx, id)POST /api/webhook/webhooks/{id}/test
WebhooksTriggerWebhook(ctx, id)POST /api/webhook/webhooks/trigger/{id}
SandboxCreateSandboxCredential(ctx, req)POST /api/sandbox/credentials
SandboxListSandboxCredentials(ctx)GET /api/sandbox/credentials
SandboxRevokeSandboxCredential(ctx, id)POST /api/sandbox/credentials/{id}/revoke
SandboxGetSMTPSandboxCredentials(ctx)GET /api/sandbox/credentials/smtp
SandboxRegenerateSMTPSandboxPassword(ctx)POST /api/sandbox/credentials/smtp/regenerate
SandboxListSandboxMessages(ctx, opts)GET /api/sandbox/messages
SandboxGetSandboxMessage(ctx, id)GET /api/sandbox/messages/{id}
SandboxClearSandboxMessages(ctx)DELETE /api/sandbox/messages
BillingGetBalance(ctx)GET /api/billing/balance
BillingInitiateTopup(ctx, amount)POST /api/billing/topup
BillingInitiatePhoneTopup(ctx, amount)POST /api/billing/topup/phone
SMSCreateSMSNotification(ctx, req)POST /api/sms/notifications
SMSListSMSNotifications(ctx, addressID)GET /api/sms/notifications
SMSRevokeSMSNotification(ctx, id)POST /api/sms/notifications/{id}/revoke
SMSListSMSDeliveries(ctx, id)GET /api/sms/notifications/{id}/deliveries
ComplianceGetComplianceProfile(ctx)GET /api/compliance/profile
ComplianceUpdateComplianceProfile(ctx, req)PATCH /api/compliance/profile
ComplianceGetAuditExport(ctx)GET /api/compliance/audit-export
AgentListAgentConfigs(ctx)GET /api/agent/configs
AgentGetAgentConfig(ctx, addressID)GET /api/agent/configs/{address_id}
AgentUpdateAgentConfig(ctx, addressID, req)PUT /api/agent/configs/{address_id}
AgentGenerateAgentDraft(ctx, addressID, req)POST /api/agent/configs/{address_id}/draft
API keysCreateAPIKey(ctx, req)POST /api/apikeys
API keysListAPIKeys(ctx)GET /api/apikeys
API keysRevokeAPIKey(ctx, id)DELETE /api/apikeys/{id}

Notes

  • Standard library only — no external HTTP client, logging, or codegen dependencies.
  • Context propagation — every method takes context.Context as its first argument.
  • Pointer helpers — examples use small local helpers strPtr, int64Ptr, boolPtr, intPtr, and timePtr that return &value, since API fields are pointers (and often optional).
  • OAuth flows — CamelAccounts/Google OAuth need browser redirects and cookies; use Register, Login, Refresh, and VerifyEmail from the SDK and handle OAuth callbacks on your frontend.
  • Single-show secrets — plaintext API keys, webhook secrets, SMS keys, and sandbox passwords are returned once; the SDK never logs them.
  • Admin routes excluded — the SDK exposes only user-facing endpoints.
  • Versioning — SemVer, currently v0.1.0. Report issues on GitHub.