Skip to content

sendaramail/sendara-go

Repository files navigation

Sendara Go SDK

Official Go client for the Sendara email API — transactional and marketing email, broadcasts, contacts, lists, templates, domains, signed inbound webhook events, and billing.

Zero non-stdlib dependencies. Typed request/response structs, a typed error type with classification helpers, automatic retries with backoff (honoring Retry-After), idempotency-key auto-generation, a cursor-based message iterator, multipart image uploads, and an outbound-webhook signature verifier.

Install

go get github.com/sendaramail/sendara-go@v0.4.0
import sendara "github.com/sendaramail/sendara-go"

Quickstart

package main

import (
	"context"
	"fmt"
	"log"

	sendara "github.com/sendaramail/sendara-go"
)

func main() {
	client := sendara.New("sk_live_...")

	resp, err := client.Emails.Send(context.Background(), sendara.EmailSendParams{
		From:    "hello@yourdomain.com",
		To:      "user@example.com",
		Subject: "Welcome",
		HTML:    "<h1>Hi there</h1>",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("queued:", resp.ID)
}

Configuration

The client takes functional options:

client := sendara.New(
	"sk_live_...",
	sendara.WithBaseURL("https://api.sendara.dev"),
	sendara.WithTimeout(15 * time.Second),
	sendara.WithMaxRetries(5),
	sendara.WithHTTPClient(myHTTPClient),
)
Option Description
WithBaseURL(url) Override the API base URL.
WithTimeout(d) Per-request timeout (default 30s; no-op with a custom HTTP client).
WithMaxRetries(n) Bound automatic retries (default 3; 0 disables).
WithHTTPClient(c) Inject a custom HTTP client / transport.
WithUserAgent(s) Append a token to the SDK User-Agent.

Errors

Every non-2xx response returns a *sendara.Error exposing Status, Code, Message, and RetryAfter. Branch on the failure class with helpers:

resp, err := client.Emails.Send(ctx, params)
if err != nil {
	var apiErr *sendara.Error
	if errors.As(err, &apiErr) {
		switch {
		case apiErr.IsRateLimited():
			time.Sleep(apiErr.RetryAfter)
		case apiErr.IsAuthentication():
			log.Fatal("bad API key")
		case apiErr.IsValidation():
			log.Printf("invalid request: %s", apiErr.Message)
		}
	}
}

Classifiers: IsAuthentication (401), IsPermission (403), IsNotFound (404), IsConflict (409), IsValidation (400/422), IsRateLimited (429), IsServer (5xx), IsTransport (network error).

Retries & idempotency

GETs and idempotent calls (sends, deletes, and PUT updates) are retried automatically on 429, 5xx, and transport errors, with exponential backoff + full jitter, honoring a server Retry-After. Non-idempotent POSTs (create contact/list/template, API-key rotation, webhook-secret rotation, etc.) are never retried.

Sends accept an IdempotencyKey; when omitted the SDK auto-generates a UUID so the call can be retried safely. Override it to dedupe across process restarts. EmailSendParams also supports TemplateID, TemplateVars, TestSend, ScheduledAt, and ValidateRecipient; scheduled sends need at least one minute of lead time to enter the scheduler.

Pagination

Messages.Iterator transparently follows the opaque cursor:

it := client.Messages.Iterator(sendara.ListMessagesParams{Channel: "email"})
for it.Next(ctx) {
	fmt.Println(it.Message().ID)
}
if err := it.Err(); err != nil {
	log.Fatal(err)
}

Or grab a single page with Messages.List (which also accepts a Search filter), or collect everything with it.All(ctx).

Fetch one message with its full event timeline, by ID or by the idempotency key from the original send:

msg, err := client.Messages.Get(ctx, sendara.MessageQuery{ID: "msg_123"})
msg, err := client.Messages.Get(ctx, sendara.MessageQuery{IdempotencyKey: "order-42"})

Billing checkout

Checkout accepts the Starter, Pro, Growth, and Scale plans with monthly or yearly billing. The result covers both a new hosted checkout and an in-place change to an existing subscription:

checkout, err := client.Billing.Checkout(ctx, sendara.CheckoutParams{
	Plan:   sendara.BillingPlanGrowth,
	Period: sendara.BillingPeriodYear,
})
if err != nil {
	log.Fatal(err)
}
if checkout.Updated {
	fmt.Println("subscription changed to", checkout.Plan)
} else {
	fmt.Println("redirect to", checkout.URL)
}

Webhook verification

Verify the signature of inbound webhook deliveries (HMAC-SHA256 of "<timestamp>.<rawBody>", hex-encoded):

func handler(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	event, err := sendara.VerifyWebhookRequest(
		webhookSigningSecret, r.Header, body, 5*time.Minute,
	)
	if err != nil {
		http.Error(w, "invalid signature", http.StatusForbidden)
		return
	}
	fmt.Println("event:", event.EventType, event.MessageID)
	w.WriteHeader(http.StatusOK)
}

VerifyWebhook(secret, timestamp, signature, body, tolerance) is the lower-level primitive. Pass tolerance == 0 to skip the replay-window check.

API coverage

Namespace Methods
client.Emails Send
client.Send Send, Batch
client.Broadcasts Create, List, Get, Send, Cancel, Delete, BulkSend
client.Messages List, Get, Iterator (+ Next/Message/Err/All)
client.Suppressions List, Create, Delete
client.Domains List, Create, Get, Verify
client.APIKeys List, Create, Rotate, Revoke
client.Usage Get, SetSpendCap
client.Billing Get, Checkout, Portal
client.Templates Create, List, Get, Update, Delete, Render
client.Contacts Create, List, Get, Update, Delete, Import
client.Contacts.Lists Create, List, Get, Update, Delete, AddMember, RemoveMember, Members
client.Webhooks Create, List, Get, Update, Delete, Deliveries, RotateSecret
client.Uploads Upload, UploadBytes
client.TestRecipients List, Create, Resend, Delete

Package-level helpers: VerifyWebhook, VerifyWebhookRequest, and the optional pointer constructors String, Bool, Int64, NullableString for building update params.

Development

cd sdk/go
go build ./...
go test ./...

License

MIT

About

Official Go SDK for the Sendara email API

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages