Skip to Content

Go SDK

The official Go client for the SendAfrica REST API. It is idiomatic, dependency-free, and exposes typed resource objects (SMS, Credits, Rates, SenderIDs, Payments, Webhooks) on the Client, plus flat methods for auth, accounts, contacts, campaigns, notifications, and health checks.

  • Module: github.com/SendAfrica/GO-SDK
  • Go 1.22 or newer. Standard library only — no third-party dependencies.
  • Base URL: https://api.sendafrica.online/v1

Install

go get github.com/SendAfrica/GO-SDK

For the full SDK guide, see the official docs at sdk.sendafrica.online/sdks/go .

Quickstart

package main import ( "context" "log" "os" sendafrica "github.com/SendAfrica/GO-SDK" ) func main() { client := sendafrica.NewClient(os.Getenv("SENDAFRICA_API_KEY")) result, err := client.SMS.Send(context.Background(), sendafrica.SendSMSRequest{ To: "0712345678", Message: "Your verification code is 4921.", }) if err != nil { log.Fatal(err) } log.Printf("sent: %s status=%s credits=%d", result.MessageID, result.Status, result.CreditsUsed) }

Keep the API key in SENDAFRICA_API_KEY (or pass it directly to NewClient). Credentials must stay server-side and out of source control.

Client setup

client := sendafrica.NewClient(apiKey, sendafrica.WithBaseURL("https://api.sendafrica.online/v1"), sendafrica.WithMaxRetries(3), sendafrica.WithUserAgent("my-app/1.0"), sendafrica.WithWebhookSecret("whsec_..."), )

If the API key argument is empty, NewClient reads SENDAFRICA_API_KEY from the environment.

OptionDescription
WithBaseURL(string)Override the default https://api.sendafrica.online/v1 (e.g. for sandbox).
WithHTTPClient(*http.Client)Supply your own http.Client (timeouts, proxies).
WithMaxRetries(int)Retry count for transient failures. Default 3.
WithBearerToken(string)Authenticate with a JWT (bearer) instead of an API key.
WithUserAgent(string)Custom User-Agent header.
WithWebhookSecret(string)Secret used by Webhooks to verify signatures.

Every request also sends an X-Request-Id and sets Accept: application/json. Responses are unwrapped from the standard {success, data, error, request_id, timestamp} envelope.

Sending SMS

Single message

result, err := client.SMS.Send(ctx, sendafrica.SendSMSRequest{ To: "0712345678", Message: "Welcome to SendAfrica!", Sender: "SENDAFRICA", // optional; defaults when omitted })
FieldTypeNotes
TostringNormalized to E.164 (+255...) automatically.
MessagestringRequired.
SenderstringOptional sender ID; must be a registered/usable ID.

Returns SMSResult with MessageID, Status, Cost, CreditsUsed, and To. Submission success is not handset delivery — use webhooks for delivery state.

Add RequestOptions{IdempotencyKey: "key-123"} to make the send replay-safe:

result, err := client.SMS.Send(ctx, sendafrica.SendSMSRequest{ To: "0712345678", Message: "Retry-safe send", }, sendafrica.RequestOptions{IdempotencyKey: "order-123"})

Send many (client-side, rate limited)

Send N distinct messages while pacing requests and collecting per-message failures instead of aborting on the first error:

res, err := client.SMS.SendMany(ctx, sendafrica.SendManyRequest{ Messages: []sendafrica.SendManyMessage{ {To: "0712345678", Message: "Hi Ali"}, {To: "0765432109", Message: "Hi Zainab", IdempotencyKey: "m2"}, }, RateLimitPerSec: 10.0, // default 10 }) // res.Results → []SMSResult, res.Failed → []BulkSMSFailedItem{Index,To,Error}

Server-side bulk SMS

Send one message to many recipients in a single API call. The SDK enforces a 100-recipient safety cap; partial failures are returned per recipient.

res, err := client.SMS.Bulk(ctx, sendafrica.BulkSMSRequest{ To: []string{"0712345678", "0765432109"}, Message: "Black Friday starts now!", Sender: "SENDAFRICA", }) // res.Total, res.Sent, res.Failed, res.Results []BulkSMSRecipient{To,Status,MessageID,CreditsUsed,Error}

Message logs

logs, err := client.SMS.Logs(ctx, sendafrica.MessageLogQuery{ Page: 1, PerPage: 25, Status: "delivered", // pending | sent | delivered | failed Search: "0712345678", DateFrom: "2026-09-01T00:00:00Z", }) // logs.Items []MessageLog, logs.Total/Page/PerPage/TotalPages

Client-side SMS analysis

client.SMS.Analyze(message) returns a local SMSPartInfo (Encoding, Length, Parts, CreditsRequired) — no network call:

info := client.SMS.Analyze("Hello world") fmt.Println(info.Encoding, info.Parts, info.CreditsRequired)

Credits

bal, err := client.Credits.Balance(ctx) // bal.AccountID, bal.Balance (integer credit count) history, err := client.Credits.History(ctx, sendafrica.PageQuery{Page: 1, PerPage: 25}) // []CreditTransaction{ID,Type,Amount,BalanceAfter,Description,CreatedAt}

Rates

The published rate card is public (no auth).

rates, err := client.Rates.List(ctx) // []CountryRate{Name,ISO2,DialCode,RateTZS} tz, err := client.Rates.Get(ctx, "TZ") // CountryRate for one country by ISO2 or name

Sender IDs

reqs, err := client.SenderIDs.Requirements(ctx) // SenderIDRequirements (eligibility, rules, docs) usable, err := client.SenderIDs.Usable(ctx, "") // []UsableSenderID available for sending list, err := client.SenderIDs.List(ctx) // []SenderID registered on the account newID, err := client.SenderIDs.Create(ctx, sendafrica.CreateSenderIDRequest{ Name: "MYBRAND", Country: "TZ", Purpose: "Marketing", SampleMessage: "Welcome to MYBRAND", // Documents: []SenderIDDocumentInput{{RequirementUID, Filename, ContentBase64}}, }) one, err := client.SenderIDs.Get(ctx, newID.ID)

Payments (vouchers)

Check the current tiered top-up rate, then create a pay-as-you-go voucher:

rate, err := client.Payments.Rate(ctx) // VoucherRate{MinAmountTZS, Tiers} payment, err := client.Payments.Create(ctx, sendafrica.CreateVoucherRequest{ Amount: 10000, // TZS — converted to credits server-side Provider: "snippe", Phone: "0712345678", }) // payment.ID, payment.Status, payment.CreditAmount

Webhooks

Verify and parse outbound events. The signature is an HMAC-SHA256 hex digest of the exact raw body in the X-SendAfrica-Signature header.

func webhookHandler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) signature := r.Header.Get("X-SendAfrica-Signature") event, err := client.Webhooks.Parse(body, signature, "") // "" uses the client-level secret if err != nil { http.Error(w, "invalid signature", http.StatusUnauthorized) return } fmt.Println(event.Type, event.MessageID, event.Data) w.WriteHeader(http.StatusOK) }

You can also verify a raw payload separately with client.Webhooks.Verify(payload, signature, secret) or the standalone sendafrica.VerifyWebhookSignature(payload, signature, secret). ParseWebhook tolerates both envelope spellings (type/message_id or status) and only ever emits the parsed event after a valid signature.

Auth, accounts, contacts, campaigns, notifications

These are available as flat methods on the client. Auth/account routes are public or bearer-token based; the rest follow the JWT or API-key auth modes.

GroupMethods
Auth & accountsRegister, Login, Refresh, SendVerificationEmail, VerifyEmail, ResetPassword, ResetPasswordConfirm, OAuthExchange, Me, UpdateMe, Logout, ChangePassword, SendPhoneOTP, VerifyPhone, ListAPIKeys, CreateAPIKey, DeleteAPIKey
ContactsListContactLists, CreateContactList, ListContacts, AddContact, GetContact, UpdateContact, DeleteContact, AddContactPhone, DeleteContactPhone, ExportContacts (CSV bytes), ImportContactsCSV, GetGoogleContactsStatus, GoogleContactsSync, GoogleContactsDisconnect
CampaignsListCampaigns, CreateCampaign, GetCampaign, CancelCampaign, DeleteCampaign, ListCampaignRecipients
NotificationsListNotifications, UnreadNotificationCount, MarkNotificationRead, MarkAllNotificationsRead
HealthHealth — unauthenticated GET /health, never retried
login, err := client.Login(ctx, "dev@example.com", "s3cret") authed := sendafrica.NewClient("", sendafrica.WithBearerToken(login.AccessToken)) profile, err := authed.Me(ctx) campaign, err := client.CreateCampaign(ctx, sendafrica.CreateCampaignRequest{ Name: "Friday blast", Message: "Weekly offer inside", ContactListID: 7, ScheduledAt: time.Now().Add(24 * time.Hour), })

Utilities

FunctionDescription
NormalizeTZPhone(string)Converts 0712345678, 255..., +255... to E.164 +255...; errors on invalid numbers.
IsValidTZPhone(string)Returns bool — validation without an error.
GetSMSPartInfo(string)Returns encoding, length, part count, required credits.
DetectEncoding(string)Returns EncodingGSM7 or EncodingUCS2.
APIKeyHash(string)SHA-256 fingerprint of a key for local identification — never store the raw key.

Errors

NewClient returns an empty client when retries are exhausted; the typed error is *sendafrica.APIError:

if err != nil { var apiErr *sendafrica.APIError if errors.As(err, &apiErr) { fmt.Println(apiErr.Code, apiErr.Message, apiErr.RequestID, apiErr.StatusCode) switch { case apiErr.IsRateLimited(): // 429 / rate_limit_exceeded case apiErr.IsInsufficientCredits(): // 402 / insufficient_credits case apiErr.IsUnauthorized(): // 401 / invalid_api_key } } if errors.Is(err, sendafrica.ErrInvalidPhone) { // local phone validation failed } }

APIError carries StatusCode, Code, Message, RequestID, RetryAfter, Body, and Headers. ErrInvalidPhone and ErrMissingAPIKey are sentinel errors you can match with errors.Is. Network-level transport failures are wrapped with sendafrica: request failed: ....

Retries and backoff

The client retries 429 and 5xx responses (and network failures) up to WithMaxRetries times (default 3) using exponential backoff from 500 ms up to a maximum of 8 s, honoring the API’s Retry-After header when present. Each attempt also sends a fresh X-Request-Id header.

Reference

Resource / MethodEndpointReturns
SMS.Send(ctx, SendSMSRequest, opts...)POST /smsSMSResult
SMS.SendMany(ctx, SendManyRequest, opts...)Loops POST /smsBulkSMSResult
SMS.Bulk(ctx, BulkSMSRequest, opts...)POST /sms/bulkBulkSendResult
SMS.Logs(ctx, MessageLogQuery, opts...)GET /sms/logsMessageLogs
SMS.Analyze(message)— (local)SMSPartInfo
Credits.Balance(ctx, opts...)GET /credits/balanceCreditBalance
Credits.History(ctx, PageQuery, opts...)GET /credits/history[]CreditTransaction
Rates.List(ctx, opts...)GET /rates[]CountryRate
Rates.Get(ctx, country, opts...)GET /rates/{country}CountryRate
SenderIDs.Requirements(ctx, opts...)GET /sender-ids/requirementsSenderIDRequirements
SenderIDs.Usable(ctx, provider, opts...)GET /sender-ids/usable[]UsableSenderID
SenderIDs.List(ctx, opts...)GET /sender-ids[]SenderID
SenderIDs.Create(ctx, CreateSenderIDRequest, opts...)POST /sender-idsSenderID
SenderIDs.Get(ctx, id, opts...)GET /sender-ids/{id}SenderID
Payments.Rate(ctx, opts...)GET /vouchers/rateVoucherRate
Payments.Create(ctx, CreateVoucherRequest, opts...)POST /vouchersPayment
Payments.CreatePayment(ctx, CreatePaymentRequest)POST /payments (legacy)Payment
Webhooks.Verify(payload, signature, secret)— (verify)error
Webhooks.Parse(payload, signature, secret)— (verify + parse)WebhookEvent
Client.Register / Login / Refresh / .../auth/*typed results
Client.ListContactLists / CreateContactList / .../contact-lists/*typed results
Client.ListCampaigns / CreateCampaign / .../campaigns/*typed results
Client.ListNotifications / .../notifications/*typed results
Client.Health(ctx)GET /healthHealthStatus

See the webhooks and SMS API references for the underlying REST contract. The Go SDK targets the same account-scoped surface documented here.

Last updated on