Go SDK
github.com/messagebird/bird-sdk-go (package bird) is the official Go SDK for the Bird API. This page covers the client itself — install, configuration, errors, retries, pagination, and webhooks. The per-method reference lives on the channel pages, starting with Email.
Install
Ejemplo de código
go get github.com/messagebird/bird-sdk-goRequires Go 1.24+. Runnable per-method examples render under each symbol on pkg.go.dev.
Create a client
bird.NewClient takes functional options from the option package. Only the API key is required — the region is inferred from the key's bk_{region}_… prefix, which selects the base URL:
Ejemplo de código
package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Email.Send(context.Background(), bird.EmailSendParams{
From: "onboarding@messagebird.dev",
To: []string{"delivered@messagebird.dev"},
Subject: "Hello from Bird",
HTML: "<p>My first Bird email.</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}Every API method is context-first and returns (T, error) — pass your request's context and cancellation propagates verbatim (context.Canceled / context.DeadlineExceeded, never wrapped in an SDK error).
Options
Options apply in order (a later option wins). Four are construction-only and return an error if passed to a single call: WithAPIKey, WithBaseURL, WithRegion, and WithHTTPClient. Everything else works both at construction (a client-wide default) and per call (an override for that one request):
Ejemplo de código
client, err := bird.NewClient(
option.WithAPIKey(os.Getenv("BIRD_API_KEY")),
option.WithTimeout(10*time.Second), // per-attempt; each retry gets a fresh budget
)
// Per-call override — disable retries for just this send.
msg, err := client.Email.Send(ctx, params, option.WithMaxRetries(0))| Option | Scope | What it does |
|---|---|---|
| WithAPIKey, WithBaseURL, WithRegion, WithHTTPClient | Construction only | Credentials, endpoint resolution, and the underlying *http.Client. |
| WithTimeout, WithMaxRetries | Construction or per call | Per-attempt timeout and the retry budget for transient failures. |
| WithIdempotencyKey | Per call | Pin the idempotency key for one mutating call (one is generated for you otherwise). |
| WithHeader | Construction or per call | Extra request headers. SDK-owned headers (Authorization, Idempotency-Key, …) win. |
| WithEmailDefaults, WithWebhookSecret, WithResponseInto | Construction or per call | Channel-wide send defaults, the webhook signing secret, and raw transport-metadata capture. |
How it's built
The wire types and a low-level client are generated from Bird's OpenAPI spec; package bird is a hand-written, idiomatic layer on top — a curated resource surface (client.Email, client.Webhooks), hand-written param structs with clean Go types ([]string, time.Time), and response types aliased to the generated models so they always match the wire. The request lifecycle — retries, timeouts, idempotency — lives in the core, not in any one resource, so every method behaves the same. The full picture of the API's shape is in SDK concepts.
Errors
Every server failure is a *bird.APIError carrying StatusCode, Type (the coarse error category), Code (the stable E##### code), Message, and RequestID for support correlation. Two variants carry extra data — *bird.RateLimitError (a 429, with RetryAfter) and *bird.ValidationError (a 422, with per-field Details) — and both unwrap to *APIError, so a single errors.As(err, &apiErr) still catches everything the server returned. Branch with errors.As:
Ejemplo de código
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
_, err = client.Email.Send(context.Background(), bird.EmailSendParams{
From: "onboarding@messagebird.dev", To: []string{"delivered@messagebird.dev"}, Subject: "Hello from Bird", HTML: "<p>My first Bird email.</p>",
})
if err != nil {
var rle *bird.RateLimitError
var ve *bird.ValidationError
var ae *bird.APIError
switch {
case errors.As(err, &rle):
fmt.Println("rate limited; retry after", rle.RetryAfter)
case errors.As(err, &ve):
for _, d := range ve.Details {
fmt.Printf("%s: %s\n", d.Param, d.Message)
}
case errors.As(err, &ae):
fmt.Printf("API error %s (status %d, request %s)\n", ae.Code, ae.StatusCode, ae.RequestID)
default:
log.Print(err) // transport: *bird.ConnectionError or *bird.TimeoutError
}
}
}Failures with no HTTP response are separate types: *bird.ConnectionError (DNS, connection refused) and *bird.TimeoutError (a single attempt exceeded its timeout). A bad webhook signature is *bird.WebhookVerificationError.
Safe retries
Transient failures — timeouts, 429s, 5xx — retry automatically (default budget: 2 retries; tune with WithMaxRetries, zero disables). For mutating calls the SDK generates one idempotency key per logical call and reuses it across every retry attempt, so a retried send can never double-deliver. Pass option.WithIdempotencyKey to pin your own key — for example, to make your own application-level retries safe too.
Pagination
List methods return an iter.Seq2[*T, error] — a lazy, range-over-func iterator that fetches pages on demand as you consume it:
Ejemplo de código
package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
for msg, err := range client.Email.List(context.Background(), bird.EmailListParams{Status: bird.EmailStatusBounced}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id)
}
page, err := client.Email.ListPage(context.Background(), bird.EmailListParams{}, "")
if err != nil {
log.Fatal(err)
}
fmt.Println(len(page.Data)) // page.NextCursor carries the next starting_after
}Breaking out of the loop stops fetching; a fetch error is yielded once and ends the sequence. For manual cursor control, ListPage returns one page plus the next cursor.
Webhooks
client.Webhooks.Unwrap verifies a Standard Webhooks signature over the raw request body and returns a typed event. Configure the signing secret with option.WithWebhookSecret (on the client or per call), and hand Unwrap the exact bytes you received — parsing and re-serializing first breaks the signature:
Ejemplo de código
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(
option.WithAPIKey(os.Getenv("BIRD_API_KEY")),
option.WithWebhookSecret(os.Getenv("BIRD_WEBHOOK_SECRET")),
)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/webhooks/bird", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
event, err := client.Webhooks.Unwrap(body, r.Header)
if err != nil {
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent) // ack fast, then process
payload, _ := event.AsAny()
switch p := payload.(type) {
case bird.EmailDeliveredEvent:
fmt.Println("delivered:", p.Data.EmailId, p.Data.Recipient)
case bird.EmailBouncedEvent:
fmt.Println("bounced:", p.Type)
}
})
}Switch on event.Type() for the discriminant, or AsAny() for the concrete payload. An unknown future event type returns an error rather than panicking, so an older SDK keeps working against a newer server.
Escape hatch
Endpoints not yet on the typed surface are reachable through client.Get / Post / Put / Patch / Delete, with the same auth, retries, and idempotency handling:
Ejemplo de código
package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
var out struct {
Data []struct {
Recipient string `json:"recipient"`
} `json:"data"`
}
if err := client.Get(context.Background(), "/v1/email/suppressions", &out); err != nil {
log.Fatal(err)
}
fmt.Println(len(out.Data))
}Find the paths in the API reference.
Next steps
- Email methods — Send, Get, List, and every param.
- Go quickstart — first send in three steps.
- SDK concepts — the cross-SDK model: errors, idempotency, pagination, webhooks.
- API reference — the underlying HTTP contract.