To send SMS from Go, add the official module with go get github.com/eSMS-Africa/esms-go, create an API key in the eSMS dashboard and call client.Messages.Send(ctx, esms.SendParams{...}). This Golang SMS API client uses only the standard library, takes a context.Context on every call and returns typed results and errors.
Last reviewed: 29 October 2026
How to send SMS with the Golang SMS API
Create an API key
Sign in at sms.esmsafrica.io and open API Keys under DEVELOPERS in the left menu. Type a Key name, pick the Environment and tick the Permissions the key needs: Send SMS and Read messages and reports cover sending and status checks. Choose Test (sandbox) while you build. Click Generate key and copy it straight away: the full key is shown once, right after you create it. Test keys start with
esms_test_, live keys withesms_live_.
1 Key name, 2 Environment (Live or Test), 3 Permissions checkboxes, 4 Generate key button, 5 API keys table. Add the module
go get github.com/eSMS-Africa/esms-go@v1.2.0The import path is
github.com/eSMS-Africa/esms-goand the package name isesms. v1.2.0 is the latest version on the Go module proxy.Send a message
Put the key in the
ESMS_API_KEYenvironment variable, then:package main import ( "context" "errors" "fmt" "log" "os" "time" esms "github.com/eSMS-Africa/esms-go" ) func main() { client := esms.New(os.Getenv("ESMS_API_KEY")) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() res, err := client.Messages.Send(ctx, esms.SendParams{ To: "+254712340001", // international (E.164) format Text: "Hello from eSMS Africa", // SenderID: "YOURBRAND", // optional: an approved sender ID }) if err != nil { var e *esms.Error if errors.As(err, &e) { // e.Code is the stable API code, e.g. country_not_enabled log.Fatalf("%d %s: %s (request %s)", e.Status, e.Code, e.Message, e.RequestID) } log.Fatal(err) } fmt.Println(res.ID, res.Status, res.Segments) }go run .prints the message id and status. With a test key the id starts withtest_and nothing is sent or charged. With a live key the status is normallysubmitted. An emptySenderIDuses the country's default sender.Read the delivery status
msg, err := client.Messages.Get(ctx, res.ID) if err != nil { log.Fatal(err) } fmt.Println(msg.Status) // queued | submitted | sent | delivered | failed ... for _, ev := range msg.Timeline { fmt.Println(ev.At, ev.Event) }In production, receive delivery reports by webhook and check them with
esms.VerifyWebhook(rawBody, r.Header.Get("X-Webhook-Signature"), secret).Branch on errors
API failures are
*esms.Errorvalues withStatus,Code,MessageandRequestID. Helpers such ase.IsInsufficientBalance(),e.IsRateLimit()ande.IsAuthentication()cover the common cases;e.Balance()ande.Cost()return the shortfall on a 402.Tune retries and timeouts
Options are passed to
esms.New:client := esms.New(os.Getenv("ESMS_API_KEY"), esms.WithMaxRetries(2), // network errors, 429 and 5xx, with backoff esms.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), )Bulk sends, retries and OTP resends are only retried on 429, never after a 5xx or network error, so they cannot run twice.
Go live
Create a live key, enable each destination country, top up and change the environment variable. The code stays the same.
Common errors and how to fix them
| What you see | What it means | How to fix it |
|---|---|---|
e.IsAuthentication() is true (401) | The key is missing or revoked. | Check os.Getenv("ESMS_API_KEY") is not empty in the running service. |
403 country_not_enabled | Live sending to that country is off for your account. Nothing was charged. | Enable it on the Countries page. Test keys do not need this. |
403 sender_not_approved | That sender ID is not approved for the destination country. | Leave SenderID empty or request it on the Sender IDs page. |
400 invalid_phone_length | The number has too many or too few digits for its country. | Fix the number. The error message gives the expected number of digits and an example. |
e.IsInsufficientBalance() is true (402) | Your balance does not cover the message. | Top up, then retry. |
context deadline exceeded | Your context timed out before the API answered. | Allow more time, and resend with the same IdempotencyKey so it cannot duplicate. |
Frequently asked questions
Does the module have third-party dependencies?
No. It uses only the Go standard library.
Can I share one client between goroutines?
Yes. The client's settings are fixed when you call esms.New and every request goes through a standard http.Client, so create one at start-up and reuse it.
Can I send OTP codes from Go?
Yes. client.Verify.Start sends a code and client.Verify.Check checks what the user typed. See the eSMS Verify guide.
How do I send personalised bulk messages?
Use client.Messages.SendBatch with a template and per-recipient Vars. See the bulk and batch guide.
Related guides
Ready? Create a free eSMS account, then create a test API key and send your first simulated message in minutes. Nothing is charged until you switch to a live key.


