Prerequisites
- A Sendbaze API key
- A verified domain. A test key works without one, and so does the shared test address for emails to yourself.
- Go 1.21 or later
Guide
1
Set your API key
export SENDBAZE_API_KEY=sk_live_xxxxxxxx
2
Send an email
main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type sendResponse struct {
ID string `json:"id"`
Status string `json:"status"`
}
type apiError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
Fields map[string][]string `json:"fields"`
} `json:"error"`
}
func main() {
body, _ := json.Marshal(map[string]any{
"from": "Yourshop <orders@yourshop.co.za>",
"to": "thandi@example.com",
"subject": "Your order #1042 has shipped",
"html": "<p>Tracking: <b>CG123456789ZA</b></p>",
})
req, _ := http.NewRequest(http.MethodPost, "https://api.sendbaze.com/v1/emails", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SENDBAZE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var e apiError
json.NewDecoder(res.Body).Decode(&e)
fmt.Println(e.Error.Code, e.Error.Message, e.Error.Fields)
return
}
var out sendResponse
json.NewDecoder(res.Body).Decode(&out)
fmt.Println(out.ID) // em_01j9...
}
3
Try it
go run main.go, then open the dashboard Logs.Examples
A small client
sendbaze/client.go
package sendbaze
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
type Client struct {
APIKey string
BaseURL string
HTTP *http.Client
}
type Error struct {
Status int
Code string
Message string
Fields map[string][]string
}
func (e *Error) Error() string { return fmt.Sprintf("sendbaze: %s (%d): %s", e.Code, e.Status, e.Message) }
func New(apiKey string) *Client {
return &Client{APIKey: apiKey, BaseURL: "https://api.sendbaze.com", HTTP: &http.Client{Timeout: 30 * time.Second}}
}
func (c *Client) Do(method, path string, in, out any, idempotencyKey string) error {
var buf bytes.Buffer
if in != nil {
if err := json.NewEncoder(&buf).Encode(in); err != nil {
return err
}
}
req, err := http.NewRequest(method, c.BaseURL+path, &buf)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("Content-Type", "application/json")
if idempotencyKey != "" {
req.Header.Set("Idempotency-Key", idempotencyKey)
}
res, err := c.HTTP.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var body struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
Fields map[string][]string `json:"fields"`
} `json:"error"`
}
json.NewDecoder(res.Body).Decode(&body)
return &Error{Status: res.StatusCode, Code: body.Error.Code, Message: body.Error.Message, Fields: body.Error.Fields}
}
if out != nil {
return json.NewDecoder(res.Body).Decode(out)
}
return nil
}
Send with a template and an idempotency key
var out struct{ ID string `json:"id"` }
err := client.Do(http.MethodPost, "/v1/emails", map[string]any{
"from": "orders@yourshop.co.za",
"to": "thandi@example.com",
"templateId": "tpl_01j9x7k2r4m8q3v5n6b7c8d9e0",
"variables": map[string]any{"name": "Thandi", "order_id": 1042, "eta": "Friday"},
}, &out, "order-1042-shipped")
Verify a webhook
func verify(rawBody []byte, header, secret string) bool {
parts := map[string]string{}
for _, kv := range strings.Split(header, ",") {
if k, v, ok := strings.Cut(kv, "="); ok {
parts[k] = v
}
}
ts, err := strconv.ParseInt(parts["t"], 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(parts["t"] + "."))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(parts["v1"]))
}