SDK
Go SDK
Official Go SDK for send0 with idiomatic Go patterns.
Installation
go get github.com/send0/send0-goRequirements: Go >= 1.21
- Zero external dependencies — stdlib only (
net/http,crypto/hmac) - Functional options pattern for configuration
- Context-first API for cancellation and timeouts
- Idiomatic error handling with typed errors
Quick start
package main
import (
"context"
"fmt"
"log"
send0 "github.com/send0/send0-go"
)
func main() {
client := send0.New("sk_live_...")
email, err := client.Emails.Send(context.Background(), &send0.SendEmailParams{
From: "hello@yourdomain.com",
To: []string{"user@example.com"},
Subject: "Welcome!",
HTML: "<p>Hello from send0</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(email.ID) // em_xxxx
fmt.Println(email.Status) // "queued"
}Available resources
| Resource | Description |
|---|---|
client.Emails | Send, get, list, batch, cancel emails |
client.Contacts | Create, update, list, delete contacts |
client.Templates | Manage email templates |
client.Domains | Domain verification and management |
Configuration
Use functional options to configure the client:
client := send0.New("sk_live_...",
send0.WithBaseURL("https://custom-proxy.example.com/v1"),
send0.WithTimeout(60 * time.Second),
send0.WithMaxRetries(5),
send0.WithHTTPClient(&http.Client{
Transport: customTransport,
}),
)| Option | Description |
|---|---|
WithBaseURL(url) | Custom API base URL |
WithTimeout(d) | Request timeout (default: 30s) |
WithMaxRetries(n) | Max retries for 5xx errors (default: 3) |
WithHTTPClient(c) | Custom *http.Client |
Error handling
email, err := client.Emails.Send(ctx, params)
if err != nil {
if send0.IsRateLimitError(err) {
rlErr := err.(*send0.RateLimitError)
fmt.Printf("Rate limited. Retry after %dms\n", rlErr.RetryAfter)
} else if send0.IsValidationError(err) {
fmt.Printf("Validation error: %s\n", err)
} else if send0.IsNotFoundError(err) {
fmt.Println("Resource not found")
} else {
fmt.Printf("Error: %s\n", err)
}
}Error types
| Type | Description |
|---|---|
*Error | API returned an error response (4xx/5xx) |
*RateLimitError | Rate limit exceeded (429), includes RetryAfter |
*ConfigError | Invalid configuration (panics on construction) |
*WebhookVerificationError | Webhook signature verification failed |
Error helpers
| Helper | Description |
|---|---|
IsNotFoundError(err) | Check if error is 404 |
IsValidationError(err) | Check if error is 422 |
IsRateLimitError(err) | Check if error is 429 rate limit |
Webhook verification
event, err := send0.VerifyWebhook(
body, // []byte raw request body
r.Header.Get("X-Send0-Signature"), // signature header
"whsec_...", // signing secret
)
if err != nil {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
fmt.Println(event.Type) // "email.delivered"Custom timestamp tolerance:
event, err := send0.VerifyWebhookWithTolerance(
body, signature, secret,
600, // 10-minute tolerance in seconds
)Sending emails
// Simple send
email, err := client.Emails.Send(ctx, &send0.SendEmailParams{
From: "Acme <hello@acme.com>",
To: []string{"user@example.com"},
Subject: "Welcome to Acme!",
HTML: "<h1>Welcome</h1><p>Thanks for joining.</p>",
Tags: map[string]string{"category": "onboarding"},
})
// With template
email, err := client.Emails.Send(ctx, &send0.SendEmailParams{
From: "hello@acme.com",
To: []string{"user@example.com"},
Subject: "Welcome!",
Template: "tmpl_welcome123",
Data: map[string]any{"name": "Jane", "company": "Acme"},
})
// With idempotency key
email, err := client.Emails.Send(ctx, &send0.SendEmailParams{
From: "hello@acme.com",
To: []string{"user@example.com"},
Subject: "Order confirmation",
HTML: "<p>Your order is confirmed.</p>",
IdempotencyKey: "order-123-confirmation",
})Batch send
batch, err := client.Emails.Batch(ctx, &send0.SendBatchParams{
From: "hello@acme.com",
Subject: "Monthly Update",
Template: "tmpl_monthly",
Recipients: []send0.BatchRecipient{
{To: []string{"jane@example.com"}, Variables: map[string]any{"name": "Jane"}},
{To: []string{"john@example.com"}, Variables: map[string]any{"name": "John"}},
},
})
fmt.Println(batch.Count) // 2Contacts
// Create
contact, err := client.Contacts.Create(ctx, &send0.CreateContactParams{
Email: "jane@example.com",
FirstName: "Jane",
LastName: "Doe",
Tags: map[string]string{"segment": "enterprise"},
})
// List with pagination
list, err := client.Contacts.List(ctx, &send0.ListContactsParams{
Limit: 50,
})
for _, c := range list.Data {
fmt.Println(c.Email)
}Templates
// Create
tmpl, err := client.Templates.Create(ctx, &send0.CreateTemplateParams{
Name: "welcome",
Subject: "Welcome to {{company}}!",
HTML: "<h1>Welcome, {{name}}!</h1>",
})
// Preview with data
preview, err := client.Templates.Preview(ctx, tmpl.ID, &send0.PreviewTemplateParams{
Data: map[string]any{"name": "Jane", "company": "Acme"},
})
fmt.Println(preview.HTML)Domains
// Add
domain, err := client.Domains.Create(ctx, &send0.CreateDomainParams{
Domain: "mail.acme.com",
})
// Print DNS records
for _, rec := range domain.DNSRecords {
fmt.Printf("%s %s → %s\n", rec.Type, rec.Name, rec.Value)
}
// Verify
result, err := client.Domains.Verify(ctx, domain.ID)
fmt.Println(result.Status) // "verified"Context and cancellation
All methods accept a context.Context as the first argument, supporting cancellation, timeouts, and deadlines:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
email, err := client.Emails.Send(ctx, params)
if err != nil {
// Could be context.DeadlineExceeded
log.Fatal(err)
}