How to Use the Twilio API in Go

Web
Use the official `go-twilio` client library to authenticate with your Account SID and Auth Token, then call the appropriate service methods (like `CreateMessage`) to interact with Twilio resources.

When you need to send an SMS from Go

You built a Go service that monitors a database connection. When the connection drops, you need to send an SMS to your phone so you can fix it. You could write raw HTTP requests, construct JSON payloads, manage authentication headers, and handle retries. Or you can use the official Twilio client library, which handles the boilerplate so you focus on the message.

The library wraps the Twilio REST API in Go methods. You pass structs and get back structs. The library serializes the data, sends the HTTP request, and deserializes the response. You interact with Go types, not raw bytes.

The Twilio client library

Twilio exposes a REST API where every resource has a URL. You authenticate with an Account SID and an Auth Token. The twilio-go library is generated from Twilio's OpenAPI specification. This means the library stays in sync with the API and covers the full surface area, including SMS, Voice, Lookups, and Verify.

The library uses the rest/api/v2010 package for the core API version. The import path reflects the API version Twilio uses. This pins your code to a specific version, preventing breaking changes when Twilio updates the API. You import the package and call methods on a client instance.

The library abstracts the HTTP layer. You work with Go types.

Minimal example: send a message

Here's the smallest program that sends an SMS. It initializes the client, sets credentials, and calls CreateMessage.

package main

import (
	"fmt"
	"log"

	"github.com/twilio/twilio-go"
	openapi "github.com/twilio/twilio-go/rest/api/v2010"
)

func main() {
	// Credentials come from environment variables in production.
	// Hardcoding them here is only for this minimal example.
	accountSID := "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
	authToken := "your_auth_token"

	// NewRestClient creates the HTTP client and configures base URLs.
	client := twilio.NewRestClient()
	// SetAuthCredentials injects the SID and token for authentication.
	client.SetAuthCredentials(accountSID, authToken)

	// CreateMessageParams holds the fields required by the API.
	// Twilio requires pointer values for optional fields to distinguish
	// between "not set" and "empty string".
	msg, err := client.CreateMessage(&openapi.CreateMessageParams{
		To:   twilio.String("+15558675309"),
		From: twilio.String("+15551234567"),
		Body: twilio.String("Hello from Go!"),
	})

	// Check the error immediately.
	// The library returns a standard Go error on HTTP failures.
	if err != nil {
		log.Fatalf("Error creating message: %v", err)
	}

	fmt.Printf("Message SID: %s\n", *msg.Sid)
}

Pointers handle optionality. Dereference with care.

What happens under the hood

NewRestClient sets up the HTTP transport and base URLs. SetAuthCredentials stores the Account SID and Auth Token. When you call CreateMessage, the library builds a POST request to /2010-04-01/Accounts/{SID}/Messages.json. It adds the Authorization header with base64-encoded credentials and encodes the parameters in the request body.

The library sends the request and reads the response. If the HTTP status is 2xx, it unmarshals the JSON into the msg struct. If the status indicates an error, it returns a Go error. The msg.Sid is a pointer because the API schema marks it as optional in some contexts. You dereference it with * to get the string value.

The twilio.String helper converts a string to a *string. Go structs initialize fields to zero values. If To were a plain string, an empty string would be the zero value. The API needs to distinguish between "no To field" and "To is empty". Pointers allow nil to represent "not set". This is a common pattern in generated Go clients.

Realistic usage: credentials and helpers

Real code loads credentials from the environment and handles multiple operations. Here's a function that sends a message and validates the destination number first.

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/twilio/twilio-go"
	openapi "github.com/twilio/twilio-go/rest/api/v2010"
)

// SendSMS initializes the client and sends a message.
// It returns the message SID or an error.
func SendSMS(to, from, body string) (string, error) {
	// Load credentials from environment variables.
	// This prevents secrets from leaking into version control.
	accountSID := os.Getenv("TWILIO_ACCOUNT_SID")
	authToken := os.Getenv("TWILIO_AUTH_TOKEN")

	if accountSID == "" || authToken == "" {
		return "", fmt.Errorf("missing Twilio credentials")
	}

	client := twilio.NewRestClient()
	client.SetAuthCredentials(accountSID, authToken)

	msg, err := client.CreateMessage(&openapi.CreateMessageParams{
		To:   twilio.String(to),
		From: twilio.String(from),
		Body: twilio.String(body),
	})

	if err != nil {
		return "", fmt.Errorf("failed to send SMS: %w", err)
	}

	return *msg.Sid, nil
}

func main() {
	sid, err := SendSMS("+15558675309", "+15551234567", "Server alert!")
	if err != nil {
		log.Fatalf("Operation failed: %v", err)
	}

	fmt.Printf("Sent message: %s\n", sid)
}

The client instance is a hub. You use it to access different resource managers. Here's how to look up a number using the same client.

// LookupNumber fetches details for a phone number.
// It demonstrates switching to a different resource manager.
func LookupNumber(client *twilio.RestClient, phoneNumber string) (*openapi.PhoneNumberInstance, error) {
	// GetPhoneNumber uses the Lookups resource.
	// The client instance is shared across all resources.
	num, err := client.GetPhoneNumber(phoneNumber)
	if err != nil {
		return nil, fmt.Errorf("lookup failed: %w", err)
	}
	return num, nil
}

Wrap errors. Load secrets from the environment.

Pitfalls and errors

If you forget to use twilio.String, the compiler rejects the program with cannot use "..." (untyped string constant) as *string value in struct literal. The API expects pointers for optional fields. If you pass the wrong type for a parameter, you get cannot use x as string value in argument.

Twilio enforces rate limits per Account SID. If you send too many requests, the API returns HTTP 429. The library returns an error. You need to handle retries with exponential backoff in your application logic. Network timeouts also return errors. Inspect the error to decide whether to retry.

The if err != nil check is verbose by design. The community accepts the boilerplate because it makes the unhappy path visible. Always check errors before using the result.

Rate limits are real. Handle errors before they become outages.

Decision: when to use the client

Use the twilio-go client library when you need to interact with Twilio resources like SMS, Voice, or Lookups without writing raw HTTP code. Use the twilio-go helper functions like twilio.String when constructing parameter structs that require pointer values for optional fields. Use environment variables for credentials when deploying to production to keep secrets out of your source code. Use error wrapping with fmt.Errorf and %w when returning errors from helper functions so callers can inspect the root cause. Use raw net/http only when you need custom transport behavior that the client library does not expose, such as specific TLS configuration or proxy settings.

Where to go next