send0
SDK

Go SDK

Official Go SDK for send0 with idiomatic Go patterns.

Installation

go get github.com/send0/send0-go

Requirements: 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

ResourceDescription
client.EmailsSend, get, list, batch, cancel emails
client.ContactsCreate, update, list, delete contacts
client.TemplatesManage email templates
client.DomainsDomain 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,
    }),
)
OptionDescription
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

TypeDescription
*ErrorAPI returned an error response (4xx/5xx)
*RateLimitErrorRate limit exceeded (429), includes RetryAfter
*ConfigErrorInvalid configuration (panics on construction)
*WebhookVerificationErrorWebhook signature verification failed

Error helpers

HelperDescription
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) // 2

Contacts

// 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)
}