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

  1. 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 with esms_live_.

    eSMS API keys page with the key name, environment, permissions, Generate key button and keys table numbered 1 to 5
    1 Key name, 2 Environment (Live or Test), 3 Permissions checkboxes, 4 Generate key button, 5 API keys table.
  2. Add the module

    go get github.com/eSMS-Africa/esms-go@v1.2.0

    The import path is github.com/eSMS-Africa/esms-go and the package name is esms. v1.2.0 is the latest version on the Go module proxy.

  3. Send a message

    Put the key in the ESMS_API_KEY environment 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 with test_ and nothing is sent or charged. With a live key the status is normally submitted. An empty SenderID uses the country's default sender.

  4. 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).

  5. Branch on errors

    API failures are *esms.Error values with Status, Code, Message and RequestID. Helpers such as e.IsInsufficientBalance(), e.IsRateLimit() and e.IsAuthentication() cover the common cases; e.Balance() and e.Cost() return the shortfall on a 402.

  6. 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.

  7. 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 seeWhat it meansHow 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_enabledLive 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_approvedThat sender ID is not approved for the destination country.Leave SenderID empty or request it on the Sender IDs page.
400 invalid_phone_lengthThe 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 exceededYour 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.

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.