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.
github.com/mailafrica/go-sdk · Go 1.25+ · MIT license.Install
go get github.com/mailafrica/go-sdkQuickstart
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)
}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 field | Default | Purpose |
|---|---|---|
BaseURL | https://api.mailafrica.online | API base URL. |
APIKey | — | Sends X-API-Key on every request (preferred). |
JWT | — | Sends Authorization: Bearer; used with TokenRefresher. |
Timeout | 30s | HTTP client timeout. |
UserAgent | mailafrica-go/0.1.0 | User-Agent header. |
TokenRefresher | — | func(ctx) (string, error) called on 401 to mint a new JWT. |
Hooks | — | Observability 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 field | Header sent | When to use |
|---|---|---|
APIKey | X-API-Key: <key> | Preferred for server-side and CLI usage. |
JWT | Authorization: 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.
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.
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.
// 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"),
})// 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.
// 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.
// 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.
// 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)// 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.
Statusissent | failed—sentmeans accepted upstream; delivery/bounce tracking is not available.
Templates
Store reusable message bodies with {{variable}} placeholders. See Templates.
// 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.
// 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
// 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.
// 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.
// 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.
// 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 paymentSMS notifications
Forward a short summary of inbound mail to a phone number. See SMS notifications.
// 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.
// 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.
// 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.
// 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.
_, 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
}
}| Helper | Backend 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.
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.
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:
| Service | Method | REST endpoint |
|---|---|---|
| Auth | Register(ctx, req) | POST /api/auth/register |
| Auth | Login(ctx, req) | POST /api/auth/login |
| Auth | GoogleLogin(ctx, idToken) | POST /api/auth/google |
| Auth | Refresh(ctx, refreshToken) | POST /api/auth/refresh |
| Auth | Me(ctx) | GET /api/auth/me |
| Auth | UpdateMe(ctx, req) | PATCH /api/auth/me |
| Auth | SetEmail(ctx, email) | POST /api/auth/email |
| Auth | VerifyEmail(ctx, token) | POST /api/auth/email/verify |
| Auth | ResendEmailVerification(ctx) | POST /api/auth/email/resend |
| Auth | SetPhone(ctx, phone) | POST /api/auth/phone |
| Auth | VerifyPhone(ctx, code) | POST /api/auth/phone/verify |
| Auth | ResendPhoneOTP(ctx) | POST /api/auth/phone/resend |
| Inbound | CreateAddress(ctx, req) | POST /api/inbound/addresses |
| Inbound | ListAddresses(ctx) | GET /api/inbound/addresses |
| Inbound | DeleteAddress(ctx, id) | DELETE /api/inbound/addresses/{id} |
| Inbound | ListMessages(ctx, opts) | GET /api/inbound/messages |
| Inbound | GetMessage(ctx, id) | GET /api/inbound/messages/{id} |
| Inbound | MarkMessageRead(ctx, id) | PATCH /api/inbound/messages/{id}/read |
| Inbound | CreateInboundDomain(ctx, domain) | POST /api/inbound/domains |
| Inbound | ListInboundDomains(ctx) | GET /api/inbound/domains |
| Inbound | VerifyInboundDomain(ctx, id) | POST /api/inbound/domains/{id}/verify |
| Inbound | DeleteInboundDomain(ctx, id) | DELETE /api/inbound/domains/{id} |
| Outbound | SendEmail(ctx, req) | POST /api/outbound/emails |
| Outbound | BatchSend(ctx, req) | POST /api/outbound/emails/batch |
| Outbound | ListSentEmails(ctx, opts) | GET /api/outbound/emails |
| Outbound | GetSentEmail(ctx, id) | GET /api/outbound/emails/{id} |
| Outbound | CreateTemplate(ctx, req) | POST /api/outbound/templates |
| Outbound | ListTemplates(ctx) | GET /api/outbound/templates |
| Outbound | GetTemplate(ctx, id) | GET /api/outbound/templates/{id} |
| Outbound | UpdateTemplate(ctx, id, req) | PATCH /api/outbound/templates/{id} |
| Outbound | DeleteTemplate(ctx, id) | DELETE /api/outbound/templates/{id} |
| Sending domains | AddSendingDomain(ctx, req) | POST /api/domains |
| Sending domains | ListSendingDomains(ctx) | GET /api/domains |
| Sending domains | VerifySendingDomain(ctx, id) | POST /api/domains/{id}/verify |
| Sending domains | DeleteSendingDomain(ctx, id) | DELETE /api/domains/{id} |
| Sender addresses | CreateSenderAddress(ctx, domainID, localPart) | POST /api/domains/{id}/senders |
| Sender addresses | ListSenderAddresses(ctx) | GET /api/domains/senders |
| Sender addresses | DeleteSenderAddress(ctx, id) | DELETE /api/domains/senders/{id} |
| Webhooks | CreateWebhook(ctx, req) | POST /api/webhook/webhooks |
| Webhooks | ListWebhooks(ctx, addressID) | GET /api/webhook/webhooks |
| Webhooks | DeleteWebhook(ctx, id) | DELETE /api/webhook/webhooks/{id} |
| Webhooks | ListWebhookDeliveries(ctx, webhookID) | GET /api/webhook/webhooks/{id}/deliveries |
| Webhooks | TestWebhook(ctx, id) | POST /api/webhook/webhooks/{id}/test |
| Webhooks | TriggerWebhook(ctx, id) | POST /api/webhook/webhooks/trigger/{id} |
| Sandbox | CreateSandboxCredential(ctx, req) | POST /api/sandbox/credentials |
| Sandbox | ListSandboxCredentials(ctx) | GET /api/sandbox/credentials |
| Sandbox | RevokeSandboxCredential(ctx, id) | POST /api/sandbox/credentials/{id}/revoke |
| Sandbox | GetSMTPSandboxCredentials(ctx) | GET /api/sandbox/credentials/smtp |
| Sandbox | RegenerateSMTPSandboxPassword(ctx) | POST /api/sandbox/credentials/smtp/regenerate |
| Sandbox | ListSandboxMessages(ctx, opts) | GET /api/sandbox/messages |
| Sandbox | GetSandboxMessage(ctx, id) | GET /api/sandbox/messages/{id} |
| Sandbox | ClearSandboxMessages(ctx) | DELETE /api/sandbox/messages |
| Billing | GetBalance(ctx) | GET /api/billing/balance |
| Billing | InitiateTopup(ctx, amount) | POST /api/billing/topup |
| Billing | InitiatePhoneTopup(ctx, amount) | POST /api/billing/topup/phone |
| SMS | CreateSMSNotification(ctx, req) | POST /api/sms/notifications |
| SMS | ListSMSNotifications(ctx, addressID) | GET /api/sms/notifications |
| SMS | RevokeSMSNotification(ctx, id) | POST /api/sms/notifications/{id}/revoke |
| SMS | ListSMSDeliveries(ctx, id) | GET /api/sms/notifications/{id}/deliveries |
| Compliance | GetComplianceProfile(ctx) | GET /api/compliance/profile |
| Compliance | UpdateComplianceProfile(ctx, req) | PATCH /api/compliance/profile |
| Compliance | GetAuditExport(ctx) | GET /api/compliance/audit-export |
| Agent | ListAgentConfigs(ctx) | GET /api/agent/configs |
| Agent | GetAgentConfig(ctx, addressID) | GET /api/agent/configs/{address_id} |
| Agent | UpdateAgentConfig(ctx, addressID, req) | PUT /api/agent/configs/{address_id} |
| Agent | GenerateAgentDraft(ctx, addressID, req) | POST /api/agent/configs/{address_id}/draft |
| API keys | CreateAPIKey(ctx, req) | POST /api/apikeys |
| API keys | ListAPIKeys(ctx) | GET /api/apikeys |
| API keys | RevokeAPIKey(ctx, id) | DELETE /api/apikeys/{id} |
Notes
- Standard library only — no external HTTP client, logging, or codegen dependencies.
- Context propagation — every method takes
context.Contextas its first argument. - Pointer helpers — examples use small local helpers
strPtr,int64Ptr,boolPtr,intPtr, andtimePtrthat return&value, since API fields are pointers (and often optional). - OAuth flows — CamelAccounts/Google OAuth need browser redirects and cookies; use
Register,Login,Refresh, andVerifyEmailfrom 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.