Quickstart · Go

Send your first email from a Go service on net/http. Bird Go client for the send, crypto/hmac for the webhook verify.

Prerequisites

  • Go 1.22 or later
  • A Bird test API key from the dashboardbird_test_xxxxxxxx

Install the SDK

go mod init example.com/bird-quickstart
go get github.com/bird-io/bird-go

Set the env vars

export BIRD_API_KEY=bird_test_xxxxxxxx
export BIRD_WEBHOOK_SECRET=whsec_xxxxxxxx

Send + receive in one file

Create main.go:

package main

import (
 "crypto/hmac"
 "crypto/sha256"
 "encoding/hex"
 "encoding/json"
 "io"
 "log"
 "net/http"
 "os"
 "strconv"
 "strings"
 "time"

 "github.com/bird-io/bird-go"
)

var client = bird.New(os.Getenv("BIRD_API_KEY"))

type sendRequest struct {
 To string `json:"to"`
}

type webhookEvent struct {
 Type string `json:"type"`
 Data struct {
  ID string `json:"id"`
 } `json:"data"`
}

func sendWelcome(w http.ResponseWriter, r *http.Request) {
 var body sendRequest
 if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
  http.Error(w, "invalid_request", http.StatusBadRequest)
  return
 }

 resp, err := client.Emails.Send(r.Context(), &bird.EmailSendParams{
  From:    "Bird <onboarding@bird.dev>",
  To:      body.To,
  Subject: "Welcome",
  HTML:    "<p>It works.</p>",
 })
 if err != nil {
  http.Error(w, err.Error(), http.StatusInternalServerError)
  return
 }

 w.Header().Set("content-type", "application/json")
 w.WriteHeader(http.StatusAccepted)
 json.NewEncoder(w).Encode(map[string]string{"id": resp.ID})
}

func birdWebhook(w http.ResponseWriter, r *http.Request) {
 raw, err := io.ReadAll(r.Body)
 if err != nil {
  http.Error(w, "invalid_request", http.StatusBadRequest)
  return
 }

 parts := map[string]string{}
 for _, p := range strings.Split(r.Header.Get("Bird-Signature"), ",") {
  if kv := strings.SplitN(p, "=", 2); len(kv) == 2 {
   parts[kv[0]] = kv[1]
  }
 }
 ts, err := strconv.ParseInt(parts["t"], 10, 64)
 if err != nil || parts["v1"] == "" {
  http.Error(w, "invalid_signature", http.StatusUnauthorized)
  return
 }
 if age := time.Now().Unix() - ts; age > 300 || age < -300 {
  http.Error(w, "invalid_signature", http.StatusUnauthorized)
  return
 }

 mac := hmac.New(sha256.New, []byte(os.Getenv("BIRD_WEBHOOK_SECRET")))
 mac.Write([]byte(parts["t"] + "."))
 mac.Write(raw)
 expected := mac.Sum(nil)

 sigBytes, err := hex.DecodeString(parts["v1"])
 if err != nil || !hmac.Equal(sigBytes, expected) {
  http.Error(w, "invalid_signature", http.StatusUnauthorized)
  return
 }

 var event webhookEvent
 if err := json.Unmarshal(raw, &event); err != nil {
  http.Error(w, "invalid_payload", http.StatusBadRequest)
  return
 }
 // event.Type == "email.delivered" | "email.bounced" | ...

 w.WriteHeader(http.StatusOK)
 json.NewEncoder(w).Encode(map[string]bool{"received": true})
}

func main() {
 mux := http.NewServeMux()
 mux.HandleFunc("POST /api/send-welcome", sendWelcome)
 mux.HandleFunc("POST /webhooks/bird", birdWebhook)
 log.Println("listening on http://localhost:8080")
 log.Fatal(http.ListenAndServe(":8080", mux))
}

Run it:

go run main.go
curl -X POST http://localhost:8080/api/send-welcome \
  -H 'content-type: application/json' \
  -d '{"to":"delivered@bird.dev"}'

What just happened

delivered@bird.dev is a sanctioned test recipient — it accepts the send, returns an email_* id, and emits the delivery event back to your webhook. The handler builds the same t.payload string the SDKs do, runs HMAC-SHA256 against the shared secret, and uses hmac.Equal for constant-time comparison.

Next steps

Starten Sie mit einem Kanal.
Fügen Sie die anderen hinzu, wenn Sie bereit sind.

Ein Test-API-Key steht Ihnen sofort zur Verfügung. Der Produktivzugang wird freigeschaltet, sobald Sie eine Zahlungsmethode hinzufügen und einen Absender verifizieren.

Jetzt startenDokumentation lesenoder

Sie nutzen Claude Code, Cursor oder Codex? Verbinden Sie es mit unserem MCP-Server – 141 Tools, eines pro API-Endpunkt, mit eingeschränkten Agent-Keys.