Send SMS with Go

Send your first text from Go with net/http and TextKit's REST API. No module to add.

Prompt for your AI agent

Paste this into Claude Code, Cursor or any coding agent to have it do the integration:

text
Add SMS sending to this Go project with TextKit.

- Docs for this stack: https://jointextkit.com/docs/send-with-go.md
- API: POST https://api.jointextkit.com/v1/messages with `Authorization: Bearer
  <key>` and a JSON body `{"to": "+1...", "body": "..."}`.
- There is no Go module: POST JSON to https://api.jointextkit.com/v1/messages
  with net/http.
- Read the key from the `TEXTKIT_API_KEY` environment variable. Never hardcode
  it or send it to the browser.
- Phone numbers are E.164 and US or Canada only (`+1` followed by 10 digits).
- A test key (`ssms_sk_test_`) simulates: `+15005550006` always ends `delivered`
  and no phone gets a text.
- Errors are JSON: `{"error": {"code", "message", "param"}}` with a 4xx status.
  On `429`, wait `Retry-After` seconds and retry.
- Send only from server code that runs after a real event (a signup, an order).
  Never expose an endpoint that texts any number a visitor types.

Before you start

  • An API key. Create a free account and copy the test key (ssms_sk_test_...) from Home in the console. It works at once, no card.
  • Go 1.18 or later.
  • Put the key in an environment variable:
bash
export TEXTKIT_API_KEY=ssms_sk_test_...

1. Send an SMS

Create main.go:

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]string{
		"to":   "+15005550006",
		"body": "Hello from TextKit",
	})

	req, err := http.NewRequest("POST", "https://api.jointextkit.com/v1/messages", bytes.NewReader(payload))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTKIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	var out struct {
		ID     string `json:"id"`
		Status string `json:"status"`
		Error  *struct {
			Code    string `json:"code"`
			Message string `json:"message"`
		} `json:"error"`
	}
	if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
		log.Fatal(err)
	}
	if out.Error != nil {
		log.Fatalf("%s: %s", out.Error.Code, out.Error.Message)
	}
	fmt.Println(out.ID, out.Status)
}

2. Run it

bash
go run main.go

It prints the message ID (msg_...) and delivered.

What the test key did

+15005550006 is a sandbox number: the send comes back delivered and no phone gets a text (the response says "simulated": true). Put your own verified phone in to and the test key texts it for real. See Sandbox & test numbers.

Handle errors

The program above already stops on an error and prints its code. A failed request returns a 4xx status and a JSON body {"error": {"code": "...", "message": "...", "param": "..."}}. On 429 rate_limited, wait the Retry-After seconds and try again. Send an Idempotency-Key header (any unique string per message) and a retried request returns the first result instead of sending twice.

StatusCodeWhat to do
400invalid_requestA field is missing or malformed; param names it.
401invalid_api_keyThe key is missing, revoked or mistyped.
402insufficient_creditsAdd credits; nothing was sent or charged.
403recipient_opted_outThey texted STOP. Do not retry.
429rate_limitedWait Retry-After seconds, then retry.

Every code is in Errors.

Go live

  1. Add a number in the console.
  2. Register your use case. We file your 10DLC registration; toll-free numbers use a guided verification form. Until carriers approve it, the number texts only your verified numbers. See Compliance & registration.
  3. Swap in a live key (ssms_sk_live_...). The code stays the same.

Next steps

  • Webhooks: delivery receipts and replies, pushed to your server.
  • Verify: one-time codes in two calls, billed only when verified.
  • Send an SMS: every field the endpoint takes.
  • All quickstarts: the same guide for other languages and frameworks.