smsgo

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 16 Imported by: 0

README

smsgo (Go)

Go Reference CI license

Official Go SDK for SMSGo — the simple SMS API for Brazil. Send OTP/2FA, transactional alerts and campaigns in a few lines of Go.

  • Integrates in minutes — auth handled for you (no manual token ritual).
  • 💸 No monthly fee — prepaid credits that don't expire, priced in BRL.
  • 🇧🇷 Brazil-first — delivery to every carrier, LGPD native.
  • 🟢 Zero dependencies — standard library only. Fully typed.
  • 🎁 R$ 10 free on sign-up — test without a card.

New account and key at smsgo.com.br → panel → My account → API.

Requirements

Go 1.21+ (uses generics for Paginated[T]).

Install

go get github.com/sms-go/smsgo-sdk-go@latest
import smsgo "github.com/sms-go/smsgo-sdk-go"

The import path is github.com/sms-go/smsgo-sdk-go; the package name is smsgo.

Quick start

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	smsgo "github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})

	res, err := client.Send(context.Background(), smsgo.SendParams{
		Phone:   "+5511999990000",
		Message: "Olá do SMSGo",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.ID, res.Status) // -> "a1b2c3...", "queued"
}

You pass only the APIKey. The SDK exchanges it for a Bearer token (valid 48h), caches it in memory (guarded by a mutex) and refreshes it automatically when it expires or the API returns 401.

Context, everywhere

Unlike the Node SDK, every network method takes a context.Context as its first argument. This is an intentional, idiomatic divergence — it lets you set deadlines and cancel in-flight requests:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res, err := client.Send(ctx, smsgo.SendParams{ /* ... */ })

Send an OTP (2FA)

n, _ := rand.Int(rand.Reader, big.NewInt(900000))
code := fmt.Sprintf("%06d", n.Int64()+100000)

_, err := client.Send(ctx, smsgo.SendParams{
	Phone:   user.Phone,
	Message: fmt.Sprintf("Seu código SMSGo é %s. Válido por 5 minutos.", code),
})
// store `code` (with a TTL) and compare it on verification

Bulk send

_, err := client.SendBulk(ctx, smsgo.SendBulkParams{
	Messages: []smsgo.BulkMessage{
		{Phone: "+5511999990000", Message: "Oi, Ana!"},
		{Phone: "+5521988887777", Message: "Oi, Bruno!"},
	},
	URLCallback: "https://yourapp.com/webhooks/smsgo", // delivery status (optional)
})

Query sends

page, _ := client.List(ctx, smsgo.ListParams{Page: 1}) // { Meta, Data []SendListItem }
one, _ := client.Get(ctx, "a1b2c3-...")                // detail + Summary{Total,Delivered,Failed,InProgress,Done}

// Track a large send without downloading everything — numbers by bucket, paginated:
failed, _ := client.GetNumbers(ctx, "a1b2c3-...", smsgo.NumbersParams{Status: "failed", Page: 1})

Test mode (sandbox)

Use the test key (prefix test_, from the panel → My account → API) as APIKey. Nothing changes in your code: sends are not dispatched and don't debit balance, responses mirror production (Test == true), and webhooks fire with the same flag.

sandbox := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_TEST_KEY")})
r, _ := sandbox.Send(ctx, smsgo.SendParams{Phone: "+5511999990000", Message: "Teste"})
r.Test // true

mode, _ := sandbox.ResolveMode(ctx) // smsgo.ModeTest  (or sandbox.Mode() after the 1st call)

Balance and catalog

bal, _ := client.GetBalance(ctx)     // { Balance: 9.3, Currency: "BRL", Company }
types, _ := client.GetSMSTypes(ctx)  // []SMSTypeItem{ ID, Name, Price, Sale } — ID goes in SMSTypeID

Buy credits (off-session)

Charges a saved card without opening the panel (the card is registered in the panel via Stripe; the API only charges an already-saved one).

plans, _ := client.Billing.Plans(ctx) // tiers by range
cards, _ := client.Billing.Cards(ctx) // last 4 digits

receipt, _ := client.Billing.Purchase(ctx, smsgo.PurchaseParams{Quantity: 5000})
receipt.Status // "succeeded" already credited the balance | "processing" confirms via webhook

invoices, _ := client.Billing.Invoices(ctx, smsgo.InvoicesParams{Page: 1})

Idempotency: each Purchase creates a new charge. On timeout, query Billing.Invoices before retrying — do not blindly retry.

Automatic recharge + balance alert

Optional fields are pointers so unset values are stripped from the request:

enabled, threshold, qty := true, 5.0, 5000
alertOn, alertAt := true, 15.0
cardID := "<uuid>"

_, err := client.SetAutoRecharge(ctx, smsgo.AutoRechargeUpdate{
	Enabled:        &enabled,
	Threshold:      &threshold, // recharge when balance ≤ R$ 5
	PlanQuantity:   &qty,       // credits per recharge
	CardID:         &cardID,    // required to enable
	AlertEnabled:   &alertOn,
	AlertThreshold: &alertAt,   // e-mail when balance ≤ R$ 15
})
cfg, _ := client.GetAutoRecharge(ctx)

Outbound webhooks (DLR + replies)

url := "https://yourapp.com/webhooks/smsgo"
cfg, _ := client.SetWebhook(ctx, smsgo.WebhookUpdate{URL: &url}) // store cfg.Secret

rotate := true
client.SetWebhook(ctx, smsgo.WebhookUpdate{RotateSecret: &rotate}) // rotate the secret

empty := ""
client.SetWebhook(ctx, smsgo.WebhookUpdate{URL: &empty}) // disable

Each request carries X-SMSGo-Signature: sha256=<hmac> — the HMAC-SHA256 of the raw body with your secret. Always verify it (constant-time):

func handler(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body) // the RAW bytes — the signature is over these
	if !smsgo.VerifyWebhookSignature(body, r.Header.Get("X-SMSGo-Signature"), secret) {
		http.Error(w, "invalid signature", http.StatusUnauthorized)
		return
	}
	// ... process the DLR / reply payload
}

VerifyWebhookSignature never panics: a tampered body, wrong secret, or an empty/truncated signature returns false.

Contacts and lists

list, _ := client.Lists.Create(ctx, smsgo.ListInput{Name: "Clientes VIP"})
contactID, _ := client.Contacts.Create(ctx, smsgo.ContactInput{
	FullName: "Ana Souza",
	Phone:    "+5511999990000",
	Email:    "ana@exemplo.com",
	Lists:    []string{list.ID},
})

client.Contacts.List(ctx, smsgo.ContactsListParams{Page: 1, Search: "ana"}) // { Meta, Data }
client.Contacts.Update(ctx, contactID, smsgo.ContactInput{FullName: "Ana S.", Phone: "+5511999990000"})
client.Contacts.Delete(ctx, contactID)

Error handling

Every non-2xx response becomes a *smsgo.Error with a Status and a stable Code. Extract it with smsgo.AsError:

_, err := client.Send(ctx, smsgo.SendParams{Phone: "+5511999990000", Message: "Olá"})
if e, ok := smsgo.AsError(err); ok {
	switch e.Code {
	case "insufficient_balance": // 402 — out of balance
	case "rate_limited":         // 429 — too many requests (see e.Details)
	case "validation_error":     // 422 — invalid data (see e.FieldErrors)
	default:
		log.Println(e.Status, e.Code, e.Message)
	}
}

On validation failures (422), e.FieldErrors carries per-field detail ([]FieldError{ Field, Message }). Transport/network failures have Status == 0 and Code == "network_error".

Code HTTP Meaning
validation_error 422 Invalid request data
unauthorized 401 Invalid key/token
insufficient_balance 402 Not enough balance
provider_out_of_stock 409 Provider stock unavailable
rate_limited 429 Rate limit reached
card_declined 402 Card declined on purchase
authentication_required 402 Card needs authentication (SCA)
card_required 400 No chargeable card
payment_unavailable 503 Payment gateway unavailable
network_error 0 Transport failure (no response)

(The API-driven codes above come straight from the response body; the SDK maps unknown statuses to http_<status>.)

API reference

smsgo.New(opts smsgo.Options) *smsgo.Client
Field Type Default Description
APIKey string Required. Your SMSGo-key.
BaseURL string https://api.smsgo.com.br Only change if SMSGo tells you to.
HTTPClient *http.Client http.DefaultClient Inject a custom client/transport.

New never panics. If APIKey is empty, methods return a *smsgo.Error (network_error, "apiKey is required") on first use.

Methods

SMS

  • Send(ctx, SendParams) (*SendResult, error) — Fields: Phone, Message, Schedule? (ISO-8601), Reference?, From?, SMSTypeID?.
  • SendBulk(ctx, SendBulkParams) (*SendResult, error) — up to 5000 messages.
  • List(ctx, ListParams) (*Paginated[SendListItem], error).
  • Get(ctx, id) (*SendDetail, error) — with Summary.
  • GetNumbers(ctx, id, NumbersParams) (*Paginated[SendNumberItem], error).
  • GetSMSTypes(ctx) ([]SMSTypeItem, error).

Account

  • GetBalance(ctx) (*Balance, error).
  • GetAutoRecharge(ctx) / SetAutoRecharge(ctx, AutoRechargeUpdate) (*AutoRechargeConfig, error).
  • GetWebhook(ctx) / SetWebhook(ctx, WebhookUpdate) (*WebhookConfig, error).
  • Mode() AuthMode / ResolveMode(ctx) (AuthMode, error).

Billing (client.Billing)

  • Plans(ctx) ([]Plan, error) · Cards(ctx) ([]Card, error) · Invoices(ctx, InvoicesParams) (*Paginated[InvoiceItem], error).
  • Purchase(ctx, PurchaseParams) (*PurchaseResult, error) — off-session, not idempotent.

Contacts (client.Contacts) and Lists (client.Lists)

  • List · Create · Get · Update · Delete.

Webhook helper (top-level)

  • VerifyWebhookSignature(body []byte, signatureHeader, secret string) bool.

Examples

Runnable programs under examples/:

SMSGO_KEY=yourkey go run ./examples/send-otp +5511999990000

Runnable Example functions also live in example_test.go and render on pkg.go.dev.

Migrating from TotalVoice / Twilio?

SMSGo focuses on simple DX and BRL pricing. No sender registration to start, no dollar billing, credits that don't expire. Full API docs: smsgo.apidog.io.

License

MIT © SMSGo

Documentation

Overview

Package smsgo is the official Go SDK for SMSGo — the simple SMS API for Brazil.

It handles the two-step authentication (SMSGo-key -> 48h Bearer token) transparently: you only pass an API key. The token is fetched on demand, cached in memory and renewed automatically when it expires or the API returns HTTP 401.

The SDK covers the whole public v1 API: sending SMS, querying sends, the SMS type catalog, account balance, billing (off-session credit purchase), automatic recharge, outbound webhooks, contacts and lists.

Quick start

client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})

res, err := client.Send(context.Background(), smsgo.SendParams{
	Phone:   "+5511999990000",
	Message: "Olá do SMSGo",
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(res.ID, res.Status)

Context

Unlike the Node SDK, every network method takes a context.Context as its first argument. This is an intentional, idiomatic divergence: it lets you set deadlines and cancel in-flight requests.

Test mode (sandbox)

A key that starts with "test_" transparently selects sandbox mode: sends are not dispatched and do not debit balance, responses mirror production (with Test == true), and webhooks fire with the same flag. The detected mode is exposed via Client.Mode and Client.ResolveMode.

Errors

Every non-2xx response is returned as a *Error carrying a stable Code and the HTTP Status. Use AsError to inspect it:

if e, ok := smsgo.AsError(err); ok && e.Code == "insufficient_balance" {
	// out of balance
}

Webhooks

Outbound webhooks are signed with HMAC-SHA256 over the raw request body. Verify the X-SMSGo-Signature header with VerifyWebhookSignature before trusting a payload.

Example (Otp)

Example_otp sends a 6-digit one-time password.

package main

import (
	"context"
	"crypto/rand"
	"fmt"
	"log"
	"math/big"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})

	n, _ := rand.Int(rand.Reader, big.NewInt(900000))
	code := fmt.Sprintf("%06d", n.Int64()+100000)

	_, err := client.Send(context.Background(), smsgo.SendParams{
		Phone:   "+5511999990000",
		Message: fmt.Sprintf("Seu código SMSGo é %s. Válido por 5 minutos.", code),
	})
	if err != nil {
		log.Fatal(err)
	}
	// Store `code` with a TTL and compare it on verification.
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func VerifyWebhookSignature

func VerifyWebhookSignature(body []byte, signatureHeader, secret string) bool

VerifyWebhookSignature reports whether signatureHeader is a valid signature for body given secret.

The expected value is "sha256=" followed by the lowercase hex HMAC-SHA256 of the raw body keyed with secret. The comparison is constant-time. It never panics: a tampered body, wrong secret, or empty/truncated signature simply returns false.

Pass the raw request body bytes (exactly as received) and the value of the X-SMSGo-Signature header.

Example

ExampleVerifyWebhookSignature verifies an incoming DLR webhook.

package main

import (
	"io"
	"net/http"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	secret := os.Getenv("SMSGO_WEBHOOK_SECRET")

	http.HandleFunc("/webhooks/smsgo", func(w http.ResponseWriter, r *http.Request) {
		// Read the RAW body — the signature is over these exact bytes.
		body, _ := io.ReadAll(r.Body)

		if !smsgo.VerifyWebhookSignature(body, r.Header.Get("X-SMSGo-Signature"), secret) {
			http.Error(w, "invalid signature", http.StatusUnauthorized)
			return
		}
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"ok":true}`))
	})
}

func VerifyWebhookSignatureWithFreshness added in v0.4.0

func VerifyWebhookSignatureWithFreshness(body []byte, signatureHeader, secret string, toleranceSeconds int) bool

VerifyWebhookSignatureWithFreshness reports whether the signature is valid AND (anti-replay) the body's sentAt is within toleranceSeconds of now. The signature is checked exactly like VerifyWebhookSignature; then, if toleranceSeconds > 0, the body is parsed and a stale or unparsable sentAt makes it return false. Pass toleranceSeconds <= 0 to skip the freshness check (equivalent to VerifyWebhookSignature). Deduplicating on the body's id field for idempotency remains the receiver's responsibility. Never panics.

Types

type AuthMode

type AuthMode string

AuthMode is the authentication mode of the current API key.

const (
	// ModeLive is a production key.
	ModeLive AuthMode = "live"
	// ModeTest is a sandbox (test_) key.
	ModeTest AuthMode = "test"
)

type AutoRechargeConfig

type AutoRechargeConfig struct {
	Enabled bool `json:"enabled"`
	// Threshold at which a recharge triggers (BRL).
	Threshold float64 `json:"threshold"`
	// PlanQuantity of credits bought on each recharge.
	PlanQuantity int     `json:"planQuantity"`
	CardID       *string `json:"cardId"`
	AlertEnabled bool    `json:"alertEnabled"`
	// AlertThreshold for the low-balance e-mail alert (BRL).
	AlertThreshold float64 `json:"alertThreshold"`
}

AutoRechargeConfig is returned by Client.GetAutoRecharge and Client.SetAutoRecharge.

type AutoRechargeUpdate

type AutoRechargeUpdate struct {
	Enabled *bool `json:"enabled,omitempty"`
	// Threshold recharges when the balance is <= this value (BRL).
	Threshold *float64 `json:"threshold,omitempty"`
	// PlanQuantity of credits bought on each recharge.
	PlanQuantity *int    `json:"plan_quantity,omitempty"`
	CardID       *string `json:"card_id,omitempty"`
	AlertEnabled *bool   `json:"alert_enabled,omitempty"`
	// AlertThreshold e-mails you when the balance is <= this value (BRL).
	AlertThreshold *float64 `json:"alert_threshold,omitempty"`
}

AutoRechargeUpdate are the parameters for Client.SetAutoRecharge. Use pointer fields so unset values are stripped from the request body.

type Balance

type Balance struct {
	// Balance available, in BRL.
	Balance  float64 `json:"balance"`
	Currency string  `json:"currency"`
	Company  Company `json:"company"`
}

Balance is returned by Client.GetBalance.

type BillingResource

type BillingResource struct {
	// contains filtered or unexported fields
}

BillingResource is the billing namespace, reachable via Client.Billing.

func (*BillingResource) Cards

func (r *BillingResource) Cards(ctx context.Context) ([]Card, error)

Cards returns the saved cards (last 4 digits only).

func (*BillingResource) Invoices

Invoices returns the invoice/receipt history (paginated).

func (*BillingResource) Plans

func (r *BillingResource) Plans(ctx context.Context) ([]Plan, error)

Plans returns the available recharge tiers.

func (*BillingResource) Purchase

func (r *BillingResource) Purchase(ctx context.Context, params PurchaseParams) (*PurchaseResult, error)

Purchase buys credits by charging a saved card (off-session). Set Quantity or PlanID. Without CardID the default card is used.

Idempotency: each call creates a new charge. On timeout, query BillingResource.Invoices before retrying — do NOT blindly retry.

Example

ExampleBillingResource_Purchase buys credits off-session.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})
	ctx := context.Background()

	receipt, err := client.Billing.Purchase(ctx, smsgo.PurchaseParams{Quantity: 5000})
	if err != nil {
		log.Fatal(err)
	}
	// "succeeded" already credited the balance; "processing" confirms via webhook.
	fmt.Println(receipt.Status, receipt.InvoiceUUID)
}

type BulkMessage

type BulkMessage struct {
	Phone     string `json:"phone"`
	Message   string `json:"message"`
	Schedule  string `json:"schedule,omitempty"`
	Reference string `json:"reference,omitempty"`
	From      string `json:"from,omitempty"`
}

BulkMessage is a single message inside SendBulkParams.

type Card

type Card struct {
	ID string `json:"id"`
	// Number is the last 4 digits.
	Number string  `json:"number"`
	Name   string  `json:"name"`
	Alias  *string `json:"alias"`
	// Validate is the expiry, MM/YY.
	Validate string `json:"validate"`
	Flag     string `json:"flag"`
	Default  bool   `json:"default"`
}

Card is a saved card from BillingResource.Cards.

type Client

type Client struct {

	// Contacts is the contacts namespace (CRUD).
	Contacts *ContactsResource
	// Lists is the lists namespace (CRUD).
	Lists *ListsResource
	// Billing is the billing namespace (plans, cards, invoices, purchase).
	Billing *BillingResource
	// contains filtered or unexported fields
}

Client is the SMSGo API client. Create one with New. It is safe for concurrent use by multiple goroutines.

func New

func New(opts Options) *Client

New creates a Client. It never panics; if APIKey is empty every network method returns an error ("apiKey is required") on first use.

func (*Client) Get

func (c *Client) Get(ctx context.Context, id string) (*SendDetail, error)

Get details a send by its UUID (includes a tracking summary).

Example

ExampleClient_Get queries the status of a send.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})
	ctx := context.Background()

	detail, err := client.Get(ctx, "a1b2c3-...")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d/%d delivered, done=%v\n",
		detail.Summary.Delivered, detail.Summary.Total, detail.Summary.Done)

	// Track a large send by bucket, paginated:
	failed, err := client.GetNumbers(ctx, detail.ID, smsgo.NumbersParams{Status: "failed", Page: 1})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("failed rows:", len(failed.Data))
}

func (*Client) GetAutoRecharge

func (c *Client) GetAutoRecharge(ctx context.Context) (*AutoRechargeConfig, error)

GetAutoRecharge reads the automatic-recharge + low-balance alert config.

func (*Client) GetBalance

func (c *Client) GetBalance(ctx context.Context) (*Balance, error)

GetBalance returns the monetary balance (BRL) plus basic account data.

Example

ExampleClient_GetBalance reads the balance and the SMS-type catalog.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})
	ctx := context.Background()

	bal, err := client.GetBalance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%.2f %s\n", bal.Balance, bal.Currency)

	types, err := client.GetSMSTypes(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, t := range types {
		fmt.Printf("#%d %s R$%.2f\n", t.ID, t.Name, t.Price)
	}
}

func (*Client) GetNumbers

func (c *Client) GetNumbers(ctx context.Context, id string, params NumbersParams) (*Paginated[SendNumberItem], error)

GetNumbers returns a send's numbers, paginated and filterable by status bucket.

func (*Client) GetSMSTypes

func (c *Client) GetSMSTypes(ctx context.Context) ([]SMSTypeItem, error)

GetSMSTypes returns the catalog of active SMS types (id = SMSTypeID).

func (*Client) GetWebhook

func (c *Client) GetWebhook(ctx context.Context) (*WebhookConfig, error)

GetWebhook reads the outbound webhook URL and secret.

func (*Client) List

func (c *Client) List(ctx context.Context, params ListParams) (*Paginated[SendListItem], error)

List lists the account's sends (paginated).

func (*Client) Mode

func (c *Client) Mode() AuthMode

Mode returns the mode (live or test) of the current key, known after the first authenticated call. It returns an empty string before then; use Client.ResolveMode to force resolution.

func (*Client) ResolveMode

func (c *Client) ResolveMode(ctx context.Context) (AuthMode, error)

ResolveMode ensures a token exists and returns the key's mode (live or test).

func (*Client) Send

func (c *Client) Send(ctx context.Context, params SendParams) (*SendResult, error)

Send sends a single SMS.

Example

ExampleClient_Send sends a single SMS.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})

	res, err := client.Send(context.Background(), smsgo.SendParams{
		Phone:   "+5511999990000",
		Message: "Olá do SMSGo",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.ID, res.Status)
}

func (*Client) SendBulk

func (c *Client) SendBulk(ctx context.Context, params SendBulkParams) (*SendResult, error)

SendBulk sends several messages in a single transaction (up to 5000).

func (*Client) SetAutoRecharge

func (c *Client) SetAutoRecharge(ctx context.Context, params AutoRechargeUpdate) (*AutoRechargeConfig, error)

SetAutoRecharge updates the automatic-recharge + alert config. To ENABLE the recharge, CardID and PlanQuantity are required.

func (*Client) SetWebhook

func (c *Client) SetWebhook(ctx context.Context, params WebhookUpdate) (*WebhookConfig, error)

SetWebhook sets the outbound webhook (DLR + replies). An empty URL disables it; RotateSecret rotates the signing secret.

Example

ExampleClient_SetWebhook configures the outbound webhook.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/sms-go/smsgo-sdk-go"
)

func main() {
	client := smsgo.New(smsgo.Options{APIKey: os.Getenv("SMSGO_KEY")})
	ctx := context.Background()

	url := "https://yourapp.com/webhooks/smsgo"
	cfg, err := client.SetWebhook(ctx, smsgo.WebhookUpdate{URL: &url})
	if err != nil {
		log.Fatal(err)
	}
	if cfg.Secret != nil {
		fmt.Println("store this secret:", *cfg.Secret)
	}
}

type Company

type Company struct {
	Name     string  `json:"name"`
	Document *string `json:"document"`
}

Company holds basic account owner data.

type ContactDetail

type ContactDetail struct {
	FullName string  `json:"fullName"`
	Email    *string `json:"email"`
	Phone    string  `json:"phone"`
}

ContactDetail is returned by ContactsResource.Get.

type ContactInput

type ContactInput struct {
	FullName string `json:"full_name"`
	Phone    string `json:"phone"`
	Email    string `json:"email,omitempty"`
	// Lists are the UUIDs of lists to associate the contact with.
	Lists []string `json:"lists,omitempty"`
}

ContactInput is the body for ContactsResource.Create and ContactsResource.Update.

type ContactsListParams

type ContactsListParams struct {
	// Page is required.
	Page    int
	PerPage int
	Search  string
	// Title filters contacts by list name.
	Title string
}

ContactsListParams are the parameters for ContactsResource.List.

type ContactsResource

type ContactsResource struct {
	// contains filtered or unexported fields
}

ContactsResource is the contacts namespace, reachable via Client.Contacts.

func (*ContactsResource) Create

func (r *ContactsResource) Create(ctx context.Context, input ContactInput) (string, error)

Create creates (or upserts by phone) a contact and returns its UUID.

func (*ContactsResource) Delete

func (r *ContactsResource) Delete(ctx context.Context, id string) (*MessageResult, error)

Delete deletes a contact.

func (*ContactsResource) Get

Get details a contact by its UUID.

func (*ContactsResource) List

List lists contacts (paginated; Page is required).

func (*ContactsResource) Update

func (r *ContactsResource) Update(ctx context.Context, id string, input ContactInput) (string, error)

Update updates a contact and returns its UUID.

type Error

type Error struct {
	// Status is the HTTP status code (0 for network/transport failures).
	Status int
	// Code is a stable error code (e.g. validation_error, insufficient_balance,
	// rate_limited). For transport failures it is "network_error".
	Code string
	// Message is a human-readable description.
	Message string
	// Details is the raw response body (parsed JSON, raw string, or nil).
	Details any
	// FieldErrors carries per-field detail on validation_error (422).
	FieldErrors []FieldError
}

Error is the standardized error returned by the SDK for non-2xx responses and transport failures. It implements the error interface.

func AsError

func AsError(err error) (*Error, bool)

AsError extracts a *Error from err, if the chain contains one.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

FieldError is a per-field validation error item.

type InvoiceCard

type InvoiceCard struct {
	Code string `json:"code"`
	Name string `json:"name"`
}

InvoiceCard is the card object of an InvoiceItem.

type InvoiceItem

type InvoiceItem struct {
	UUID      string         `json:"uuid"`
	Total     float64        `json:"total"`
	Date      string         `json:"date"`
	Expiry    string         `json:"expiry"`
	DisplayID int            `json:"displayId"`
	Status    *InvoiceStatus `json:"status"`
	Card      *InvoiceCard   `json:"card"`
}

InvoiceItem is one row of BillingResource.Invoices.

type InvoiceStatus

type InvoiceStatus struct {
	Code  string  `json:"code"`
	Name  string  `json:"name"`
	Icon  *string `json:"icon"`
	Color *string `json:"color"`
}

InvoiceStatus is the status object of an InvoiceItem.

type InvoicesParams

type InvoicesParams struct {
	Page    int
	PerPage int
}

InvoicesParams are the parameters for BillingResource.Invoices.

type ListInput

type ListInput struct {
	// Name of the list (2–20 characters).
	Name string `json:"name"`
}

ListInput is the body for ListsResource.Create and ListsResource.Update.

type ListParams

type ListParams struct {
	// Page number (defaults to 1 when zero).
	Page int
}

ListParams are the parameters for Client.List.

type ListResult

type ListResult struct {
	Name string `json:"name"`
	ID   string `json:"id"`
}

ListResult is returned by list CRUD methods.

type ListsListParams

type ListsListParams struct {
	// Page is required.
	Page    int
	PerPage int
	Title   string
}

ListsListParams are the parameters for ListsResource.List.

type ListsResource

type ListsResource struct {
	// contains filtered or unexported fields
}

ListsResource is the lists namespace, reachable via Client.Lists.

func (*ListsResource) Create

func (r *ListsResource) Create(ctx context.Context, input ListInput) (*ListResult, error)

Create creates a list.

func (*ListsResource) Delete

func (r *ListsResource) Delete(ctx context.Context, id string) (*MessageResult, error)

Delete deletes a list.

func (*ListsResource) Get

func (r *ListsResource) Get(ctx context.Context, id string) (*ListResult, error)

Get details a list by its UUID.

func (*ListsResource) List

func (r *ListsResource) List(ctx context.Context, params ListsListParams) (*Paginated[map[string]any], error)

List lists the account's lists (paginated; Page is required).

func (*ListsResource) Update

func (r *ListsResource) Update(ctx context.Context, id string, input ListInput) (*ListResult, error)

Update updates a list.

type MessageResult

type MessageResult struct {
	Message string `json:"message"`
}

MessageResult is the {message} response of delete endpoints.

type NumbersParams

type NumbersParams struct {
	// Status filters by bucket: "delivered", "failed" or "in_progress".
	Status string
	Page   int
}

NumbersParams are the parameters for Client.GetNumbers.

type Options

type Options struct {
	// APIKey is the permanent account key (panel -> My account -> API). A key
	// starting with "test_" transparently selects sandbox mode. Required.
	APIKey string
	// BaseURL overrides the API base. Default: https://api.smsgo.com.br.
	BaseURL string
	// HTTPClient overrides the HTTP client. Default: http.DefaultClient.
	HTTPClient *http.Client
}

Options configures a Client.

type Paginated

type Paginated[T any] struct {
	Meta PaginationMeta `json:"meta"`
	Data []T            `json:"data"`
}

Paginated is a generic paginated response.

type PaginationMeta

type PaginationMeta struct {
	Total           int     `json:"total"`
	PerPage         int     `json:"perPage"`
	CurrentPage     int     `json:"currentPage"`
	LastPage        int     `json:"lastPage"`
	FirstPage       int     `json:"firstPage"`
	FirstPageURL    string  `json:"firstPageUrl"`
	LastPageURL     string  `json:"lastPageUrl"`
	NextPageURL     *string `json:"nextPageUrl"`
	PreviousPageURL *string `json:"previousPageUrl"`
}

PaginationMeta describes a paginated result page.

type Plan

type Plan struct {
	ID       string  `json:"id"`
	Quantity int     `json:"quantity"`
	Price    float64 `json:"price"`
	Sale     float64 `json:"sale"`
	// Unit is the effective unit price (BRL).
	Unit float64 `json:"unit"`
	// Total of the package (BRL).
	Total   float64 `json:"total"`
	Popular bool    `json:"popular"`
}

Plan is a recharge tier from BillingResource.Plans.

type PurchaseParams

type PurchaseParams struct {
	// Quantity of credits (250–1,000,000). Ignored when PlanID is set.
	Quantity int `json:"quantity,omitempty"`
	// PlanID is a package UUID (tier). Takes priority over Quantity.
	PlanID string `json:"plan_id,omitempty"`
	// CardID is a saved-card UUID (optional; uses the default card when empty).
	CardID string `json:"card_id,omitempty"`
	// Coupon code (optional).
	Coupon string `json:"coupon,omitempty"`
}

PurchaseParams are the parameters for BillingResource.Purchase.

type PurchaseResult

type PurchaseResult struct {
	// Status "succeeded" already credited the balance; "processing" confirms via webhook.
	Status      string `json:"status"`
	InvoiceUUID string `json:"invoiceUuid"`
	// Total charged (BRL).
	Total           float64 `json:"total"`
	Quantity        int     `json:"quantity"`
	PaymentIntentID string  `json:"paymentIntentId"`
}

PurchaseResult is returned by BillingResource.Purchase.

type SMSTypeItem

type SMSTypeItem struct {
	// ID is the value to pass as SMSTypeID.
	ID   int    `json:"id"`
	Name string `json:"name"`
	// Price is the unit price (BRL).
	Price float64 `json:"price"`
	// Sale is the promotional unit price (BRL), if any.
	Sale *float64 `json:"sale"`
}

SMSTypeItem is one row of Client.GetSMSTypes.

type SendBulkParams

type SendBulkParams struct {
	// Messages holds up to 5000 messages per request.
	Messages []BulkMessage `json:"messages"`
	// URLCallback receives delivery-status callbacks (optional).
	URLCallback string `json:"urlCallback,omitempty"`
	// FlashSms sends as a flash SMS if the provider supports it (optional). It is
	// a pointer so an explicit false is still transmitted (nil is stripped),
	// mirroring the Node SDK's stripUndefined semantics.
	FlashSms *bool `json:"flashSms,omitempty"`
	// SMSTypeID selects a pricing tier (optional).
	SMSTypeID int `json:"sms_type_id,omitempty"`
}

SendBulkParams are the parameters for Client.SendBulk.

type SendDetail

type SendDetail struct {
	ID         string             `json:"id"`
	Quantity   int                `json:"quantity"`
	Characters int                `json:"characters"`
	Date       *string            `json:"date"`
	Total      float64            `json:"total"`
	Cost       float64            `json:"cost"`
	User       string             `json:"user"`
	Status     string             `json:"status"`
	Type       string             `json:"type"`
	Summary    SendSummary        `json:"summary"`
	Phones     []SendNumberDetail `json:"phones"`
}

SendDetail is returned by Client.Get.

type SendListItem

type SendListItem struct {
	ID        string  `json:"id"`
	Number    *int    `json:"number"`
	Date      *string `json:"date"`
	Quantity  int     `json:"quantity"`
	FullName  string  `json:"full_name"`
	CreatedAt string  `json:"created_at"`
	Status    string  `json:"status"`
	Type      string  `json:"type"`
}

SendListItem is one row of Client.List.

type SendNumberDetail

type SendNumberDetail struct {
	ID         string  `json:"id"`
	Characters int     `json:"characters"`
	Code       *string `json:"code"`
	Cost       float64 `json:"cost"`
	Message    string  `json:"message"`
	Phone      string  `json:"phone"`
	Status     string  `json:"status"`
	Template   *string `json:"template"`
	CreatedAt  string  `json:"created_at"`
}

SendNumberDetail is a per-number entry inside SendDetail.

type SendNumberItem

type SendNumberItem struct {
	ID        string  `json:"id"`
	Phone     string  `json:"phone"`
	Code      *string `json:"code"`
	Status    string  `json:"status"`
	CreatedAt string  `json:"created_at"`
}

SendNumberItem is one row of Client.GetNumbers.

type SendParams

type SendParams struct {
	// Phone in international E.164 format, e.g. +5511999990000.
	Phone string `json:"phone"`
	// Message text (1–1600 characters; the real limit depends on the provider).
	Message string `json:"message"`
	// Schedule is an optional ISO-8601 timestamp.
	Schedule string `json:"schedule,omitempty"`
	// Reference is your own identifier, echoed back in webhooks (optional).
	Reference string `json:"reference,omitempty"`
	// From is the sender, as supported by the provider (optional).
	From string `json:"from,omitempty"`
	// SMSTypeID selects a pricing tier (optional). See [Client.GetSMSTypes].
	SMSTypeID int `json:"sms_type_id,omitempty"`
}

SendParams are the parameters for Client.Send.

type SendResult

type SendResult struct {
	// ID is the send UUID.
	ID       string `json:"id"`
	Quantity int    `json:"quantity"`
	// Status is "scheduled" when scheduled, otherwise "queued".
	Status string `json:"status"`
	// Test is true only in sandbox mode.
	Test bool `json:"test,omitempty"`
}

SendResult is returned by Client.Send and Client.SendBulk.

type SendSummary

type SendSummary struct {
	Total      int `json:"total"`
	Delivered  int `json:"delivered"`
	Failed     int `json:"failed"`
	InProgress int `json:"inProgress"`
	// Done is true when no number is still in progress.
	Done bool `json:"done"`
}

SendSummary holds status-bucket counts for a send.

type WebhookConfig

type WebhookConfig struct {
	// URL configured (nil = disabled).
	URL *string `json:"url"`
	// Secret is the HMAC secret. Sign the raw body to validate X-SMSGo-Signature.
	Secret *string `json:"secret"`
}

WebhookConfig is returned by Client.GetWebhook and Client.SetWebhook.

type WebhookUpdate

type WebhookUpdate struct {
	// URL is your HTTPS endpoint. An empty string disables the webhook.
	URL *string `json:"url,omitempty"`
	// RotateSecret generates a new signing secret.
	RotateSecret *bool `json:"rotate_secret,omitempty"`
}

WebhookUpdate are the parameters for Client.SetWebhook.

Directories

Path Synopsis
examples
buy-credits command
Buys credits off-session and configures automatic recharge.
Buys credits off-session and configures automatic recharge.
check-balance command
Prints the account balance and the SMS-type catalog.
Prints the account balance and the SMS-type catalog.
check-status command
Sends a bulk batch and then queries its delivery status.
Sends a bulk batch and then queries its delivery status.
configure-webhook command
Configures the outbound webhook (DLR + replies).
Configures the outbound webhook (DLR + replies).
receive-dlr-webhook command
Receives delivery-status (DLR) and reply webhooks, verifying the signature.
Receives delivery-status (DLR) and reply webhooks, verifying the signature.
send-otp command
Sends a 6-digit OTP / 2FA code.
Sends a 6-digit OTP / 2FA code.
send-sms command
Sends a single SMS.
Sends a single SMS.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL