anypost

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 19 Imported by: 0

README

Anypost Go SDK

The official Go client for the Anypost email API.

Requires Go 1.23+. Zero dependencies (standard library only). Every call takes a context.Context and is safe for concurrent use.

This README covers the SDK itself: installation, idioms, and configuration. For platform concepts and the full field-level API reference, see the Anypost documentation.

Install

go get github.com/anypost/anypost-go
import "github.com/anypost/anypost-go"

The package name is anypost.

Quickstart

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anypost/anypost-go"
)

func main() {
	client, err := anypost.New("ap_your_api_key")
	if err != nil {
		log.Fatal(err)
	}

	sent, err := client.Email.Send(context.Background(), &anypost.SendEmailRequest{
		From:    "YourCo <you@yourdomain.com>",
		To:      []string{"you@example.com"},
		Subject: "Welcome to Anypost",
		HTML:    "<p>Hello, inbox!</p>",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(sent.ID)
}

anypost.New("") reads the key from ANYPOST_API_KEY instead. Keep the key server-side; it is a bearer credential.

Sending

One of Text, HTML, or TemplateID is required. All recipients in To, CC, and BCC share one envelope and count against a combined limit of 50.

sent, err := client.Email.Send(ctx, &anypost.SendEmailRequest{
	From:    "YourCo <you@yourdomain.com>",
	To:      []string{"a@example.com", "b@example.com"},
	CC:      []string{"team@example.com"},
	ReplyTo: []string{"support@yourdomain.com"},
	Subject: "Receipt #4823",
	HTML:    "<p>Thanks for your order.</p>",
	Text:    "Thanks for your order.",
	Tags:    []string{"receipt"},
})

Attachment.Content is the raw file bytes: pass what os.ReadFile returns and the SDK base64-encodes it on the wire. Do not pre-encode it. The request body is capped at 5 MB.

pdf, err := os.ReadFile("report.pdf")
if err != nil {
	log.Fatal(err)
}

_, err = client.Email.Send(ctx, &anypost.SendEmailRequest{
	From:    "YourCo <you@yourdomain.com>",
	To:      []string{"someone@example.com"},
	Subject: "Your report",
	Text:    "Attached.",
	Attachments: []anypost.Attachment{
		{Filename: "report.pdf", Content: pdf},
	},
})

Send with a published template and per-recipient variables:

_, err := client.Email.Send(ctx, &anypost.SendEmailRequest{
	From:       "YourCo <you@yourdomain.com>",
	To:         []string{"someone@example.com"},
	TemplateID: "template_018f2c5e-3a40-7a91-9c25-3a0b1d5e6f78",
	Variables:  map[string]any{"name": "Ada", "plan": "pro"},
})

See the send reference for the complete field list.

Batch

Send 1 to 100 independent messages in one request. Defaults fills any field an entry omits. Leave an entry's From (and any other shared field) zero to inherit the default; an entry that sets its own value wins. To is always per-entry.

result, err := client.Email.SendBatch(ctx, &anypost.EmailBatchRequest{
	Defaults: &anypost.SendEmailRequest{From: "YourCo <you@yourdomain.com>"},
	Emails: []anypost.SendEmailRequest{
		{To: []string{"a@example.com"}, Subject: "Hi A", Text: "..."},
		{To: []string{"b@example.com"}, Subject: "Hi B", Text: "..."},
	},
})

A batch with mixed outcomes returns HTTP 207 and does not return an error. Inspect each entry's Status rather than treating it as a failure:

fmt.Printf("%+v\n", result.Summary) // {Total, Queued, Failed}

for _, entry := range result.Data {
	if entry.Status == "queued" {
		fmt.Println(entry.Index, entry.ID)
	} else {
		fmt.Println(entry.Index, entry.Error.Type, entry.Error.Message)
	}
}

Domains

Manage sending domains under client.Domains. Add a domain, publish the records it returns, then verify.

domain, err := client.Domains.Create(ctx, &anypost.DomainCreateParams{Name: "example.com"})
if err != nil {
	log.Fatal(err)
}
for _, r := range domain.DNSRecords {
	fmt.Printf("%s %s -> %s\n", r.Type, r.Name, r.Value)
}

checked, err := client.Domains.Verify(ctx, domain.ID)
if err != nil {
	log.Fatal(err)
}
if checked.Status != "verified" && checked.VerificationFailure != nil {
	// Verify returns the current domain even while pending; it is not an error.
	fmt.Println(checked.VerificationFailure.Code)
}

Get, Update (tracking config only), and Delete round out the resource. See Domains for the verification lifecycle and field reference.

API keys

Manage keys under client.APIKeys. The plaintext secret comes back only once, on Create, as Key:

created, err := client.APIKeys.Create(ctx, &anypost.APIKeyCreateParams{
	Name:           "Production server",
	Permissions:    anypost.PermissionSendOnly,
	AllowedDomains: []string{"example.com"},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(created.Key) // store now; never retrievable again

Get returns metadata only (KeyPrefix, never the secret); Update and Delete round out the resource. See API keys for the permission model and cache propagation.

Templates

Templates use a draft/published model: edits land in a draft, and Publish promotes it. A template can't be used for sending until it's published.

tmpl, err := client.Templates.Create(ctx, &anypost.TemplateCreateParams{
	Name: "Welcome email",
	Kind: anypost.TemplateKindHTML,
	HTML: anypost.String("<h1>Welcome, {{ name }}</h1>"),
})
if err != nil {
	log.Fatal(err)
}

_, err = client.Templates.Publish(ctx, tmpl.ID)

The pointer-string fields (Subject, HTML, Markdown) distinguish "unset" from an explicit empty string. anypost.String is a helper for setting them.

Kind (html or markdown) is immutable once set. GetDraft, UpdateDraft, DeleteDraft, Duplicate, Get, Update (name only), and Delete round out the resource. Send a published template with TemplateID (see Sending). See Templates for the full model.

Suppressions

A suppression blocks sends to an address, scoped to a Topic. The wildcard * blocks every topic; a specific topic (e.g. marketing) leaves transactional traffic untouched.

_, err := client.Suppressions.Create(ctx, &anypost.SuppressionCreateParams{
	Email: "alice@example.com",
	Topic: "marketing",
	Note:  "Customer requested removal",
})

err = client.Suppressions.Delete(ctx, "alice@example.com", "marketing")

Get, List (with EmailContains, Topic, Reason, and Origin filters), ListForEmail, and DeleteForEmail round out the resource. See Suppressions for scoping and the automatic-suppression rules for bounces and complaints.

Webhooks

Manage webhook subscriptions under client.Webhooks. The SigningSecret comes back only once, on Create; later reads return only SigningSecretPrefix.

wh, err := client.Webhooks.Create(ctx, &anypost.WebhookCreateParams{
	Name:   "Production events",
	URL:    "https://hooks.example.com/anypost",
	Events: []anypost.WebhookEventType{anypost.WebhookEventDelivered, anypost.WebhookEventBounced, anypost.WebhookEventComplained},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(wh.SigningSecret) // store now; never retrievable again

Update, Test, RotateSecret, Get, List, and Delete round out the resource. See Webhooks for the event catalog, status transitions, and the secret-rotation grace window.

Verifying deliveries

anypost.VerifyWebhookSignature and anypost.UnwrapWebhookEvent are plain functions: they need the signing secret, not an API key, so call them in your handler without a client. Pass the raw request body (the exact bytes, before JSON parsing), the Anypost-Signature header, and the secret.

func handler(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	sig := r.Header.Get("Anypost-Signature")

	delivery, err := anypost.UnwrapWebhookEvent(body, sig, signingSecret)
	if err != nil {
		var verr *anypost.WebhookVerificationError
		errors.As(err, &verr) // verr.Reason: ReasonNoMatch, ReasonTimestampOutOfTolerance, ...
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	for _, event := range delivery.Events {
		// event.Type, event.Data["email_id"], ...
	}
	w.WriteHeader(http.StatusOK)
}

Reach for VerifyWebhookSignature when something else has already parsed the body: keep the raw bytes for the verify step, then use your parsed value once it passes. Deliveries older than five minutes are rejected by default to bound replay; WithTolerance widens, narrows, or disables (0) that check, and WithNow overrides the clock in tests. During a secret rotation the header carries a v1= component per active secret, and a match on any one passes, so deliveries keep verifying while you redeploy.

Events

client.Events.List pages the team's event stream, newest-first. The window defaults to the last 24 hours and is clamped to your plan's retention. Events are read-only and not addressable by id, so there is no Get.

page, err := client.Events.List(ctx, anypost.EventListParams{EventType: anypost.EventBounced})
if err != nil {
	log.Fatal(err)
}
for _, e := range page.Data {
	fmt.Println(e.OccurredAt, e.Recipient, e.BounceClassification)
}

Filter by Start, End, EventType, Recipient, EmailID, MessageID, Domain, Topic, Campaign, TemplateID, IPPool, and Tags, a slice that matches an event carrying any of the given tags. Every other filter is exact-match. This is also how you backfill the gap after a webhook endpoint was disabled: page the events that occurred during the outage once it's healthy. See Events for the field reference.

Pagination

List endpoints return a *Page[T] with Data, HasMore, and NextCursor. Read one page, call Next to fetch the following one, or range over All to walk every item across pages, re-fetching as it goes.

page, err := client.Domains.List(ctx, anypost.ListParams{Limit: 50})
page.Data       // this page's items
page.HasMore    // whether another page exists
page.NextCursor // pass to ListParams.After to fetch it yourself

for domain, err := range page.All(ctx) { // every domain, across all pages
	if err != nil {
		return err
	}
	fmt.Println(domain.Name)
}

Errors

A failed request returns an *anypost.Error. Recover it with errors.As and switch on Type, which is the stable, machine-readable error.type. Branch on it rather than on the HTTP status.

sent, err := client.Email.Send(ctx, message)
if err != nil {
	var apiErr *anypost.Error
	if errors.As(err, &apiErr) {
		switch apiErr.Type {
		case anypost.ErrorTypeValidation:
			fmt.Println(apiErr.ValidationErrors) // field -> messages
		case anypost.ErrorTypeRateLimit:
			fmt.Println(apiErr.RetryAfter) // time.Duration
		default:
			fmt.Println(apiErr.Type, apiErr.Status, apiErr.RequestID)
		}
	}
	return err
}
Type constant error.type Status
ErrorTypeValidation validation_error 400, 422
ErrorTypeAuthentication authentication_error 401
ErrorTypePermission permission_error 403
ErrorTypeNotFound not_found 404
ErrorTypeConflict / ErrorTypeIdempotencyConflict / ErrorTypeWebhookRotation conflict, idempotency_concurrent, webhook_rotation_in_progress 409
ErrorTypeIdempotencyMismatch idempotency_mismatch 422
ErrorTypeRateLimit rate_limit_exceeded 429
ErrorTypePayloadTooLarge payload_too_large 413
ErrorTypeInternal / ErrorTypeProvisioning internal_error, provisioning_error 5xx
ErrorTypeConnection connection_error none

Every API-level error carries Type, Status, RequestID, Message, and the raw Body. A connection error (no response) carries ErrorTypeConnection, a zero Status, and the underlying transport error via errors.Unwrap.

Retries and idempotency

The client retries 429, 502, 503, and network failures up to maxRetries times (default 2), with exponential backoff and full jitter. It honors Retry-After.

Sends are made safe to retry automatically: when retries are enabled and you do not pass an idempotency key, the client generates one and reuses it across attempts, so a retried send cannot deliver twice. Pass your own key to dedupe across process restarts:

client.Email.Send(ctx, message, anypost.WithIdempotencyKey("order-4823"))

Configuration

client, err := anypost.New("ap_your_api_key",
	anypost.WithBaseURL("https://api.anypost.com/v1"),
	anypost.WithTimeout(30*time.Second),
	anypost.WithMaxRetries(2),
	anypost.WithHTTPClient(&http.Client{}),
	anypost.WithDefaultHeader("X-My-Header", "value"),
)
Option Default Description
WithBaseURL https://api.anypost.com/v1 API base URL.
WithTimeout 30s Per-request timeout, composed with the call's context.
WithMaxRetries 2 Automatic retries for transient failures.
WithHTTPClient &http.Client{} Custom client/transport (proxy, TLS, tests).
WithDefaultHeader none Extra header sent on every request (repeatable).

Pass an empty string as the API key to read ANYPOST_API_KEY from the environment.

License

MIT

Documentation

Overview

Package anypost is the official Go client for the Anypost email API.

Create a client with an API key (or set ANYPOST_API_KEY and pass an empty string), then call resource methods. Every method takes a context.Context for cancellation and timeout.

client, err := anypost.New("ap_your_api_key")
if err != nil {
    log.Fatal(err)
}

sent, err := client.Email.Send(ctx, &anypost.SendEmailRequest{
    From:    "Acme <you@yourdomain.com>",
    To:      []string{"someone@example.com"},
    Subject: "Hello",
    HTML:    "<p>It worked.</p>",
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(sent.ID)

A failed call returns an *anypost.Error; recover it with errors.As and branch on its Type. Keep the API key server-side; it is a bearer credential.

Index

Constants

View Source
const DefaultWebhookTolerance = 300 * time.Second

DefaultWebhookTolerance is the default maximum age of a webhook delivery, measured from its signed timestamp. Deliveries older than this are rejected to bound replay of a captured request.

View Source
const Version = "1.4.0"

Version is the SDK version, reported in the User-Agent header. It is the single source of truth: bump it, tag the commit `vX.Y.Z`, and push — the release workflow publishes the module proxy entry for the tag.

Variables

This section is empty.

Functions

func Bool

func Bool(v bool) *bool

Bool is a helper for setting an optional *bool field, e.g. Tracking{Opens: anypost.Bool(true)}.

func String added in v1.2.0

func String(v string) *string

String is a helper for setting an optional *string field, e.g. TemplateCreateParams{HTML: anypost.String("<h1>Hi</h1>")}.

func VerifyWebhookSignature

func VerifyWebhookSignature(payload []byte, signatureHeader, secret string, opts ...VerifyOption) error

VerifyWebhookSignature verifies the signature on an Anypost webhook delivery.

Pass the raw request body (the exact bytes received, before JSON parsing), the Anypost-Signature header value, and the webhook's signing secret. It returns nil on success and a *WebhookVerificationError otherwise.

The header may carry more than one v1= component during a secret rotation; a match on any one passes, so deliveries keep verifying across a rotation.

Types

type APIKey

type APIKey struct {
	// ID is the key_-prefixed id.
	ID   string `json:"id"`
	Name string `json:"name"`
	// KeyPrefix is the first 12 characters of the key, shown for identification.
	KeyPrefix   string      `json:"key_prefix"`
	Permissions Permissions `json:"permissions"`
	// AllowedDomains lists the domains this key may send from. nil means all
	// verified domains.
	AllowedDomains []string `json:"allowed_domains"`
	// AllowedIPs lists the IPs/CIDRs allowed to use this key. nil means all IPs.
	AllowedIPs []string `json:"allowed_ips"`
	// LastUsedAt is when the key was last used, or nil if never.
	LastUsedAt *string `json:"last_used_at"`
	CreatedAt  string  `json:"created_at"`
}

APIKey is an API key's metadata. The plaintext secret is never returned here.

type APIKeyCreateParams

type APIKeyCreateParams struct {
	Name        string      `json:"name"`
	Permissions Permissions `json:"permissions"`
	// AllowedDomains restricts sending to these domains. Omit for all verified.
	AllowedDomains []string `json:"allowed_domains,omitempty"`
	// AllowedIPs restricts use to these IPs/CIDRs. Omit for all IPs.
	AllowedIPs []string `json:"allowed_ips,omitempty"`
}

APIKeyCreateParams is the body for APIKeysService.Create.

type APIKeyUpdateParams

type APIKeyUpdateParams struct {
	Name        string      `json:"name"`
	Permissions Permissions `json:"permissions"`
	// AllowedDomains restricts sending to these domains. Pass an empty slice to
	// lift the restriction.
	AllowedDomains []string `json:"allowed_domains,omitempty"`
	// AllowedIPs restricts use to these IPs/CIDRs. Pass an empty slice to lift it.
	AllowedIPs []string `json:"allowed_ips,omitempty"`
}

APIKeyUpdateParams is the body for APIKeysService.Update. The plaintext secret is not rotated here.

type APIKeyWithSecret

type APIKeyWithSecret struct {
	APIKey
	// Key is the full API key. Store it securely; it cannot be retrieved later.
	Key string `json:"key"`
}

APIKeyWithSecret is a newly created key, including its plaintext secret. The secret is returned only once, at creation.

type APIKeysService

type APIKeysService struct {
	// contains filtered or unexported fields
}

APIKeysService holds the /api-keys operations. Access it via Client.APIKeys.

func (*APIKeysService) Create

Create issues a new API key. The plaintext secret is returned only in this response, as Key — store it securely; it cannot be retrieved later.

func (*APIKeysService) Delete

func (s *APIKeysService) Delete(ctx context.Context, id string, opts ...RequestOption) error

Delete removes a key. It may keep authenticating for up to 5 minutes due to gateway caching.

func (*APIKeysService) Get

func (s *APIKeysService) Get(ctx context.Context, id string, opts ...RequestOption) (*APIKey, error)

Get retrieves a single API key's metadata. The secret is never returned.

func (*APIKeysService) List

func (s *APIKeysService) List(ctx context.Context, params ListParams, opts ...RequestOption) (*Page[APIKey], error)

List returns one page of the team's API keys, newest-first.

func (*APIKeysService) Update

func (s *APIKeysService) Update(ctx context.Context, id string, params *APIKeyUpdateParams, opts ...RequestOption) (*APIKey, error)

Update changes a key's name, permissions, and restrictions. The secret is not rotated here. Changes may take up to 5 minutes to propagate.

type Attachment

type Attachment struct {
	// Filename is the file name shown to the recipient.
	Filename string `json:"filename"`
	// Content is the raw file bytes; encoded to base64 on the wire.
	Content []byte `json:"content"`
	// ContentType is the MIME type. Defaults to application/octet-stream
	// server-side when empty.
	ContentType string `json:"content_type,omitempty"`
	// ContentID marks the attachment inline, referenced from the HTML via cid:.
	ContentID string `json:"content_id,omitempty"`
}

Attachment is one inline attachment on a message.

Content is the raw file bytes (for example, the result of os.ReadFile). The SDK base64-encodes it on the wire via Go's standard JSON encoding of a byte slice — do not pre-encode it.

type BatchItemError

type BatchItemError struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

BatchItemError is the inner error on a failed batch entry.

type BatchItemResult

type BatchItemResult struct {
	Status string `json:"status"`
	// Index is the zero-based position in the request Emails slice.
	Index     int             `json:"index"`
	ID        string          `json:"id,omitempty"`
	CreatedAt string          `json:"created_at,omitempty"`
	Error     *BatchItemError `json:"error,omitempty"`
}

BatchItemResult is one entry's outcome in a batch send. Discriminate on Status: "queued" entries carry ID and CreatedAt; "failed" entries carry Error.

type BatchResponse

type BatchResponse struct {
	Summary BatchSummary      `json:"summary"`
	Data    []BatchItemResult `json:"data"`
}

BatchResponse is returned from a batch send. A mixed-outcome batch (HTTP 207) is a success, not an error: inspect each entry's Status. Data[i].Index == i.

type BatchSummary

type BatchSummary struct {
	Total  int `json:"total"`
	Queued int `json:"queued"`
	Failed int `json:"failed"`
}

BatchSummary tallies a batch's per-entry outcomes.

type Client

type Client struct {
	// Email holds send operations (/email, /email/batch).
	Email *EmailService
	// Domains holds sending-domain operations (/domains).
	Domains *DomainsService
	// APIKeys holds API-key operations (/api-keys).
	APIKeys *APIKeysService
	// Templates holds template operations (/templates), including draft/publish.
	Templates *TemplatesService
	// Suppressions holds suppression-list operations (/suppressions).
	Suppressions *SuppressionsService
	// Webhooks holds webhook operations (/webhooks), including test and rotation.
	Webhooks *WebhooksService
	// Events holds read access to the event stream (/events).
	Events *EventsService
	// contains filtered or unexported fields
}

Client is the entry point to the Anypost API. Construct it with New. It is safe for concurrent use by multiple goroutines.

func New

func New(apiKey string, opts ...Option) (*Client, error)

New creates a Client. If apiKey is empty, it falls back to the ANYPOST_API_KEY environment variable; if neither is set, it returns an error.

func (*Client) Whoami

func (c *Client) Whoami(ctx context.Context, opts ...RequestOption) (*WhoamiResponse, error)

Whoami identifies the team and permission level behind the current API key.

type DNSRecord

type DNSRecord struct {
	// Type is the record type. "CNAME" is the only value today.
	Type string `json:"type"`
	// Name is the record name to publish, relative to the registered apex.
	Name string `json:"name"`
	// Value is the CNAME target (absolute FQDN).
	Value string `json:"value"`
	// Purpose is one of "verification", "dkim", or "tracking".
	Purpose string `json:"purpose"`
}

DNSRecord is a DNS record the customer must publish to verify a domain or its branded tracking.

type Domain

type Domain struct {
	// ID is the domain_-prefixed id.
	ID string `json:"id"`
	// Name is the domain name, e.g. example.com.
	Name string `json:"name"`
	// Status is "pending" until the mail-flow CNAMEs resolve, then "verified".
	Status string `json:"status"`
	// DNSRecords holds the mail-flow records to publish.
	DNSRecords []DNSRecord `json:"dns_records"`
	// VerificationFailure is the most recent mail-flow failure, or nil.
	VerificationFailure *VerificationFailure `json:"verification_failure"`
	// Tracking is the branded tracking configuration and its status.
	Tracking  DomainTracking `json:"tracking"`
	CreatedAt string         `json:"created_at"`
	// VerifiedAt is when the domain last transitioned to verified, or nil.
	VerifiedAt *string `json:"verified_at"`
}

Domain is a sending domain and its mail-flow verification state.

type DomainCreateParams

type DomainCreateParams struct {
	// Name is the domain to add, e.g. example.com.
	Name string `json:"name"`
}

DomainCreateParams is the body for DomainsService.Create.

type DomainTracking

type DomainTracking struct {
	OpensEnabled  bool `json:"opens_enabled"`
	ClicksEnabled bool `json:"clicks_enabled"`
	// Subdomain is the tracking subdomain prefix, or nil when tracking is off.
	Subdomain *string `json:"subdomain"`
	// DNSRecords holds the branded-tracking records to publish. Empty when off.
	DNSRecords []DNSRecord `json:"dns_records"`
	// Status is "disabled", "pending", or "verified".
	Status string `json:"status"`
	// VerificationFailure is the most recent tracking-CNAME failure, or nil.
	VerificationFailure *VerificationFailure `json:"verification_failure"`
	// VerifiedAt is when the tracking CNAME was last observed resolving, or nil.
	VerifiedAt *string `json:"verified_at"`
}

DomainTracking is a domain's branded open/click tracking configuration. It is independent of mail-flow verification.

type DomainTrackingParams

type DomainTrackingParams struct {
	OpensEnabled  *bool `json:"opens_enabled,omitempty"`
	ClicksEnabled *bool `json:"clicks_enabled,omitempty"`
	// Subdomain is the tracking subdomain prefix. Required when either tracking
	// flag is turned on; leave nil to keep the current value unchanged.
	Subdomain *string `json:"subdomain,omitempty"`
}

DomainTrackingParams is the mutable tracking configuration on an update. Leave a pointer nil to leave that field unchanged.

type DomainUpdateParams

type DomainUpdateParams struct {
	Tracking DomainTrackingParams `json:"tracking"`
}

DomainUpdateParams is the body for DomainsService.Update. Only tracking configuration is mutable; the domain name is immutable.

type DomainsService

type DomainsService struct {
	// contains filtered or unexported fields
}

DomainsService holds the /domains operations. Access it via Client.Domains.

func (*DomainsService) Create

func (s *DomainsService) Create(ctx context.Context, params *DomainCreateParams, opts ...RequestOption) (*Domain, error)

Create adds a sending domain. The returned domain is pending until verified.

func (*DomainsService) Delete

func (s *DomainsService) Delete(ctx context.Context, id string, opts ...RequestOption) error

Delete permanently removes a domain and its DKIM keys.

func (*DomainsService) Get

func (s *DomainsService) Get(ctx context.Context, id string, opts ...RequestOption) (*Domain, error)

Get retrieves a single domain by id.

func (*DomainsService) List

func (s *DomainsService) List(ctx context.Context, params ListParams, opts ...RequestOption) (*Page[Domain], error)

List returns one page of the team's domains, newest-first. Range over the returned page's All method to walk every page, or follow NextCursor yourself.

func (*DomainsService) Update

func (s *DomainsService) Update(ctx context.Context, id string, params *DomainUpdateParams, opts ...RequestOption) (*Domain, error)

Update changes a domain's tracking configuration. The domain name is immutable.

func (*DomainsService) Verify

func (s *DomainsService) Verify(ctx context.Context, id string, opts ...RequestOption) (*Domain, error)

Verify triggers a verification check. It always returns the current domain — read Status and VerificationFailure to learn the outcome; a still-pending domain is not an error. Safe to poll while DNS propagates.

type EmailBatchRequest

type EmailBatchRequest struct {
	// Defaults fills any field an entry omits. To is excluded — recipients are
	// always per-entry. Reuse SendEmailRequest, leaving To zero.
	Defaults *SendEmailRequest `json:"defaults,omitempty"`
	// Emails holds the 1-100 messages in the batch.
	Emails []SendEmailRequest `json:"emails"`
}

EmailBatchRequest is the body for a batch send: 1-100 messages, with optional batch-wide defaults.

type EmailService

type EmailService struct {
	// contains filtered or unexported fields
}

EmailService holds the /email operations. Access it via Client.Email.

func (*EmailService) Send

func (s *EmailService) Send(ctx context.Context, email *SendEmailRequest, opts ...RequestOption) (*SendResponse, error)

Send sends a single message. All addresses in To/CC/BCC share one envelope. It returns the queued message id; a failure returns an *Error.

When retries are enabled and no WithIdempotencyKey is supplied, the client generates one so a retried send cannot deliver twice. Pass WithIdempotencyKey to dedupe across process restarts.

func (*EmailService) SendBatch

func (s *EmailService) SendBatch(ctx context.Context, batch *EmailBatchRequest, opts ...RequestOption) (*BatchResponse, error)

SendBatch sends 1-100 independent messages in one request. A mixed-outcome batch (HTTP 207) returns normally — inspect each entry's Status in Data; it does not return an error.

type Error

type Error struct {
	// Type is the stable, machine-readable error type. Branch on this.
	Type ErrorType
	// Message is the human-readable description from the API.
	Message string
	// Status is the HTTP status code, or 0 when no response was received.
	Status int
	// RequestID is the server-assigned request id, when the response carried
	// one. Quote it in support requests.
	RequestID string
	// ValidationErrors maps a field path to its list of problems. Populated
	// only for ErrorTypeValidation.
	ValidationErrors map[string][]string
	// RetryAfter is the server-advised wait before retrying. Populated only for
	// ErrorTypeRateLimit when the response carried a Retry-After header.
	RetryAfter time.Duration
	// Body is the raw response body, for inspection beyond the parsed fields.
	Body []byte
	// contains filtered or unexported fields
}

Error is the single error type returned by every SDK call that fails. A request that reached the API and came back non-2xx carries Type, Status, and (when sent) RequestID; a request that never got a response carries ErrorTypeConnection, a zero Status, and a wrapped cause.

Use errors.As to recover it, then switch on Type:

var apiErr *anypost.Error
if errors.As(err, &apiErr) {
    switch apiErr.Type {
    case anypost.ErrorTypeValidation:
        // apiErr.ValidationErrors: field -> messages
    case anypost.ErrorTypeRateLimit:
        // apiErr.RetryAfter
    }
}

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause for a connection error, enabling errors.Is/errors.As against the wrapped transport error.

type ErrorType

type ErrorType string

ErrorType is the stable, machine-readable classification of an API error. Branch on this rather than on the HTTP status: the type is part of the API contract, the status is not.

const (
	ErrorTypeValidation          ErrorType = "validation_error"
	ErrorTypeAuthentication      ErrorType = "authentication_error"
	ErrorTypePermission          ErrorType = "permission_error"
	ErrorTypeNotFound            ErrorType = "not_found"
	ErrorTypeConflict            ErrorType = "conflict"
	ErrorTypeIdempotencyConflict ErrorType = "idempotency_concurrent"
	ErrorTypeIdempotencyMismatch ErrorType = "idempotency_mismatch"
	ErrorTypeWebhookRotation     ErrorType = "webhook_rotation_in_progress"
	ErrorTypeRateLimit           ErrorType = "rate_limit_exceeded"
	ErrorTypePayloadTooLarge     ErrorType = "payload_too_large"
	ErrorTypeProvisioning        ErrorType = "provisioning_error"
	ErrorTypeInternal            ErrorType = "internal_error"
	// ErrorTypeConnection is set when no HTTP response was received (a network
	// failure, timeout, or context cancellation). Status is then 0 and the
	// underlying cause is available via errors.Unwrap.
	ErrorTypeConnection ErrorType = "connection_error"
)

type Event

type Event struct {
	// ID is the stable id for log correlation. Not addressable — there is no
	// GET /events/{id}.
	ID   string    `json:"id"`
	Type EventType `json:"type"`
	// OccurredAt is the ISO 8601 UTC timestamp when the event was observed.
	OccurredAt string `json:"occurred_at"`
	// EmailID is the email_<uuidv7> id minted when the message was accepted.
	EmailID *string `json:"email_id"`
	// MessageID is the RFC 5322 Message-ID: header, when one was stamped.
	MessageID *string `json:"message_id"`
	// From is the envelope From: address.
	From *string `json:"from"`
	// FromDomain is the From: domain, lowercased.
	FromDomain *string `json:"from_domain"`
	// Recipient is the single recipient this event refers to.
	Recipient *string `json:"recipient"`
	// Subject is the captured Subject: header, truncated at the capture limit.
	Subject *string `json:"subject"`
	// Campaign is the originating send's campaign value.
	Campaign *string `json:"campaign"`
	// TemplateID is the public id of the template the originating send used.
	TemplateID *string `json:"template_id"`
	// Topic is the send-time topic the message was tagged with.
	Topic *string `json:"topic"`
	// Tags are the customer-supplied tags from the originating send.
	Tags []string `json:"tags"`
	// IPPool is which dedicated IP pool the message egressed from. Nil on
	// sends that named no pool and on accounts without dedicated IPs. Set on
	// every event for the message, not just email.sent, so bounce and
	// complaint rates can be read per pool.
	IPPool *string `json:"ip_pool"`
	// SMTPCode is the SMTP reply code observed, or nil without an SMTP exchange.
	SMTPCode *int `json:"smtp_code"`
	// BounceType is why the message failed. Only on email.bounced, and one of:
	//   "permanent" — the receiver refused the address outright (5xx). The
	//                 address is suppressed; this is what counts against list
	//                 quality.
	//   "transient" — a temporary failure still unresolved when the message
	//                 was reported, e.g. an out-of-band 4xx DSN.
	//   "expired"   — aged out of the retry queue after 72 hours without ever
	//                 reaching the receiver. Not a hard bounce, and the
	//                 address is not suppressed.
	BounceType *string `json:"bounce_type"`
	// BounceClassification is the bounce classification. Only on email.bounced.
	BounceClassification *string `json:"bounce_classification"`
	// Attempt is the delivery attempt number, or nil for non-delivery events.
	Attempt *int `json:"attempt"`
	// Tracking is the tracking metadata, mirroring the webhook payload's
	// data.tracking. Nil on every event except opens/clicks, and on human
	// opens/clicks. Its Bot is set when the open/click came from a mailbox
	// image proxy.
	Tracking *EventTracking `json:"tracking"`
}

Event is a single email-pipeline event for the team. Every field is always present; fields that don't apply to a given event type are null on the wire (nil pointers / zero values here) rather than absent.

type EventBot added in v1.1.0

type EventBot struct {
	// Source is the detected mailbox image proxy, e.g. "google", "yahoo", "bing".
	Source string `json:"source"`
	// Kind is always "proxy" on customer-visible events.
	Kind string `json:"kind"`
}

EventBot classifies a proxied open or click. Pure-noise machine traffic (mailbox prefetchers, scanners) never becomes an event, so the only Kind a customer ever sees is "proxy" — a real open whose origin is anonymized by a mailbox image proxy (Gmail, Yahoo, etc.).

type EventListParams

type EventListParams struct {
	ListParams
	// Start is the ISO 8601 start of the window (inclusive).
	Start string
	// End is the ISO 8601 end of the window (exclusive).
	End       string
	EventType EventType
	// Recipient is an exact recipient address.
	Recipient string
	// EmailID restricts to one message's email_<uuidv7> id.
	EmailID string
	// MessageID is an exact Message-ID: header match.
	MessageID string
	// Domain is a sending-domain hostname (not the domain_<uuid> id).
	Domain string
	Topic  string
	// Campaign is a case-sensitive exact match.
	Campaign string
	// TemplateID is the template the originating send used.
	TemplateID string
	// IPPool restricts to mail that egressed from this named dedicated IP
	// pool. Exact match against the [a-z0-9]([a-z0-9-]*[a-z0-9])? pool-name
	// shape; a value outside it returns an empty list rather than being
	// ignored, so a typo cannot silently widen the answer to "all pools".
	IPPool string
	// Tags restricts to events carrying any of these tags (hasAny). Up to 10.
	Tags []string
}

EventListParams are the filters for EventsService.List. The window defaults to the last 24 hours and is clamped to the plan's retention. All filters are exact-match except Tags (hasAny).

type EventTracking added in v1.1.0

type EventTracking struct {
	Bot *EventBot `json:"bot,omitempty"`
}

EventTracking is the tracking metadata on email.opened / email.clicked events, mirroring the webhook payload's data.tracking. Bot is set only when the interaction came from a mailbox image proxy; a human open/click has no Bot.

type EventType

type EventType string

EventType is a customer-facing event type in the event stream. The same set is emitted via webhooks; operational events are never returned here.

const (
	EventSent         EventType = "email.sent"
	EventDelivered    EventType = "email.delivered"
	EventDelayed      EventType = "email.delayed"
	EventBounced      EventType = "email.bounced"
	EventComplained   EventType = "email.complained"
	EventSuppressed   EventType = "email.suppressed"
	EventUnsubscribed EventType = "email.unsubscribed"
	EventOpened       EventType = "email.opened"
	EventClicked      EventType = "email.clicked"
)

type EventsService

type EventsService struct {
	// contains filtered or unexported fields
}

EventsService holds read access to the /events stream. List-only — events are not addressable by id. Access it via Client.Events.

func (*EventsService) List

func (s *EventsService) List(ctx context.Context, params EventListParams, opts ...RequestOption) (*Page[Event], error)

List returns one page of the team's events, newest-first.

type IdentityService

type IdentityService struct {
	// contains filtered or unexported fields
}

IdentityService holds the /whoami operation.

func (*IdentityService) Whoami

func (s *IdentityService) Whoami(ctx context.Context, opts ...RequestOption) (*WhoamiResponse, error)

Whoami identifies the team and permission level behind the current API key.

type ListParams

type ListParams struct {
	// Limit is the page size, 1-100. Zero uses the server default (20).
	Limit int
	// After is a cursor from a previous page's NextCursor. Opaque — do not parse.
	After string
}

ListParams are the cursor-pagination parameters shared by every list endpoint. A zero value requests the first page with the server default size.

type Option

type Option func(*clientConfig)

Option configures a Client in New.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the API base URL. Defaults to the production endpoint.

func WithDefaultHeader

func WithDefaultHeader(name, value string) Option

WithDefaultHeader adds a header sent on every request.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a custom *http.Client. Use this to inject a transport (proxy, custom TLS, or a test RoundTripper). The client's own per-request timeout still applies on top via context.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the number of automatic retries for transient failures (429/502/503 and network errors). Defaults to 2. Set 0 to disable.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout. Defaults to 30s. A zero or negative value disables the client-imposed timeout (the context still applies).

type Page

type Page[T any] struct {
	// Data holds the items on this page.
	Data []T
	// HasMore reports whether another page exists.
	HasMore bool
	// NextCursor is the cursor for the next page, or "" when there are none.
	// Pass it back as ListParams.After to fetch that page yourself.
	NextCursor string
	// contains filtered or unexported fields
}

Page is one page of a list result. It mirrors the wire envelope (Data, HasMore, NextCursor); call Next to fetch the following page, or range over All to walk every remaining item across pages.

func (*Page[T]) All

func (p *Page[T]) All(ctx context.Context) iter.Seq2[T, error]

All returns an iterator over every item across this and all following pages, re-fetching as it goes. A fetch failure ends iteration and yields a non-nil error as the second value:

for domain, err := range page.All(ctx) {
    if err != nil {
        return err
    }
    // use domain
}

func (*Page[T]) Next

func (p *Page[T]) Next(ctx context.Context) (*Page[T], error)

Next fetches the following page, or returns (nil, nil) when there are none.

type Permissions

type Permissions string

Permissions is the permission level of an API key.

const (
	// PermissionFull grants management and send access.
	PermissionFull Permissions = "full"
	// PermissionSendOnly grants send access only.
	PermissionSendOnly Permissions = "send_only"
)

type RequestOption

type RequestOption func(*requestConfig)

RequestOption overrides behavior for a single call.

func WithHeader

func WithHeader(name, value string) RequestOption

WithHeader adds (or overrides) a header on a single request.

func WithIdempotencyKey

func WithIdempotencyKey(key string) RequestOption

WithIdempotencyKey sets the Idempotency-Key for a send. Reusing a key with an identical body replays the stored result; reusing it with a different body fails with ErrorTypeIdempotencyMismatch. Only the send endpoints honor it.

type SendEmailRequest

type SendEmailRequest struct {
	// From is the sender address on a verified domain, bare or
	// "Display Name <addr@host>". Required for a standalone send; omit on a
	// batch entry to inherit Defaults.From.
	From string `json:"from,omitempty"`
	// To holds 1-50 primary recipients. Combined To+CC+BCC must be <= 50.
	To []string `json:"to,omitempty"`
	CC []string `json:"cc,omitempty"`
	// BCC recipients. Counts against the combined recipient cap.
	BCC []string `json:"bcc,omitempty"`
	// ReplyTo holds one address or up to 10.
	ReplyTo []string `json:"reply_to,omitempty"`
	// Subject is required unless a referenced template supplies it.
	Subject string `json:"subject,omitempty"`
	Text    string `json:"text,omitempty"`
	HTML    string `json:"html,omitempty"`
	// TemplateID references a published template (template_<uuid>). Cannot be
	// combined with inline Text/HTML.
	TemplateID string `json:"template_id,omitempty"`
	// Headers are custom message headers. At most 25 survive server-side.
	Headers map[string]string `json:"headers,omitempty"`
	// Attachments holds up to 20 inline attachments.
	Attachments []Attachment `json:"attachments,omitempty"`
	// Tags holds up to 10 free-form labels ([A-Za-z0-9_-]{1,64}).
	Tags []string `json:"tags,omitempty"`
	// Campaign is a stream-segmentation label ([A-Za-z0-9_-]{1,64}).
	Campaign string `json:"campaign,omitempty"`
	// Topic is the suppression scope / topic bucket ([a-z0-9_.-]{1,64}).
	Topic string `json:"topic,omitempty"`
	// IPPool names which of your dedicated IP pools this message sends from
	// ([a-z0-9]([a-z0-9-]*[a-z0-9])?, at most 32 chars). Accounts with
	// dedicated IPs and more than one named pool only. Unlike Tags, Topic and
	// Campaign this is not a reporting label — it changes how the message is
	// delivered, keeping one stream's reputation and queueing off another's.
	// Leave zero to use the account's default pool; an unrecognized name
	// returns 422 listing the pools the account does have.
	IPPool string `json:"ip_pool,omitempty"`
	// Tracking overrides the domain's open/click defaults for this message.
	Tracking *Tracking `json:"tracking,omitempty"`
	// Variables is the Handlebars substitution map. Encoded JSON must be <= 64 KB.
	Variables map[string]any `json:"variables,omitempty"`
	// Unsubscribe controls one-click unsubscribe header injection.
	Unsubscribe *Unsubscribe `json:"unsubscribe,omitempty"`
}

SendEmailRequest is a single message to send.

For a standalone Send, From and To are required, and at least one of Text, HTML, or TemplateID must be set (the API enforces this). As a batch entry, From (and any other shared field) may be omitted to inherit the batch Defaults — leave it zero and set EmailBatchRequest.Defaults.

type SendResponse

type SendResponse struct {
	// ID is the public message identifier (email_<uuidv7>).
	ID        string `json:"id"`
	CreatedAt string `json:"created_at"`
}

SendResponse is returned by a successful single send.

type Suppression

type Suppression struct {
	// ID is the sup_-prefixed id, for log correlation. Lookups/deletes key on
	// (email, topic).
	ID string `json:"id"`
	// Email is the suppressed address, normalized to lowercase.
	Email string `json:"email"`
	// Topic this suppression applies to. "*" means every topic.
	Topic  string            `json:"topic"`
	Reason SuppressionReason `json:"reason"`
	Origin SuppressionOrigin `json:"origin"`
	// Classification is a bounce classification or ARF feedback-type, nil for
	// manual entries.
	Classification *string `json:"classification"`
	// SMTPCode is the SMTP reply code from the bounce, nil for complaints and
	// manual entries.
	SMTPCode *int `json:"smtp_code"`
	// Note is a free-form note attached at creation.
	Note *string `json:"note"`
	// SuppressedAt is when the suppression was first observed.
	SuppressedAt string `json:"suppressed_at"`
	// ExpiresAt is when it stops applying, nil means never.
	ExpiresAt *string `json:"expires_at"`
	CreatedAt string  `json:"created_at"`
}

Suppression is a suppressed recipient address, scoped to a topic.

type SuppressionCreateParams

type SuppressionCreateParams struct {
	Email string `json:"email"`
	// Topic scopes the suppression. Omit or "*" to block every topic.
	Topic string `json:"topic,omitempty"`
	// Note is an optional internal annotation, preserved across automatic
	// re-suppressions.
	Note string `json:"note,omitempty"`
}

SuppressionCreateParams is the body for SuppressionsService.Create.

type SuppressionListParams

type SuppressionListParams struct {
	ListParams
	// EmailContains is a case-insensitive substring match against the address.
	EmailContains string
	// Topic restricts to a topic. "*" for global entries.
	Topic  string
	Reason SuppressionReason
	Origin SuppressionOrigin
}

SuppressionListParams are the filters for SuppressionsService.List.

type SuppressionOrigin

type SuppressionOrigin string

SuppressionOrigin is the provenance of a suppression row.

const (
	SuppressionOriginAuto   SuppressionOrigin = "auto"
	SuppressionOriginManual SuppressionOrigin = "manual"
)

type SuppressionReason

type SuppressionReason string

SuppressionReason is why an address is suppressed.

const (
	SuppressionReasonPermanentBounce SuppressionReason = "permanent_bounce"
	SuppressionReasonComplaint       SuppressionReason = "complaint"
	SuppressionReasonUnsubscribed    SuppressionReason = "unsubscribed"
	SuppressionReasonManual          SuppressionReason = "manual"
)

type SuppressionsService

type SuppressionsService struct {
	// contains filtered or unexported fields
}

SuppressionsService holds the /suppressions operations. Entries key on (email, topic). Access it via Client.Suppressions.

func (*SuppressionsService) Create

Create adds a manual suppression. Defaults to topic "*" (every topic). It returns a validation_error if an active entry for the same (email, topic) exists.

func (*SuppressionsService) Delete

func (s *SuppressionsService) Delete(ctx context.Context, email, topic string, opts ...RequestOption) error

Delete removes the single (email, topic) row. Other topics are untouched.

func (*SuppressionsService) DeleteForEmail

func (s *SuppressionsService) DeleteForEmail(ctx context.Context, email string, opts ...RequestOption) error

DeleteForEmail removes an address from the suppression list across every topic.

func (*SuppressionsService) Get

func (s *SuppressionsService) Get(ctx context.Context, email, topic string, opts ...RequestOption) (*Suppression, error)

Get retrieves the suppression for an (email, topic) pair. Use "*" as the topic for the global row. It returns a not_found error if the pair isn't suppressed.

func (*SuppressionsService) List

List returns one page of the team's suppressions, newest-first. Expired rows are filtered out.

func (*SuppressionsService) ListForEmail

func (s *SuppressionsService) ListForEmail(ctx context.Context, email string, opts ...RequestOption) ([]Suppression, error)

ListForEmail returns every suppression on file for an address, across all topics. It returns a not_found error if the address has no active suppressions.

type Template

type Template struct {
	// ID is the template_-prefixed id.
	ID string `json:"id"`
	// Name is the identifier, unique within the team.
	Name string `json:"name"`
	// Subject is the published subject line, nil until first published.
	Subject *string      `json:"subject"`
	Kind    TemplateKind `json:"kind"`
	// HTML is the published HTML body, nil until first published.
	HTML *string `json:"html"`
	// Text is the published, machine-derived plain-text body, nil until first
	// published.
	Text *string `json:"text"`
	// Markdown is the published emailmd source, set only for kind=markdown.
	Markdown *string `json:"markdown"`
	// HasDraft reports whether an unpublished draft is pending.
	HasDraft bool `json:"has_draft"`
	// PublishedAt is when last published, or nil if never.
	PublishedAt *string `json:"published_at"`
	CreatedAt   string  `json:"created_at"`
	UpdatedAt   string  `json:"updated_at"`
}

Template is a reusable email template. The Subject/HTML/Text/Markdown fields hold the published content and are nil until first published. Edits land in a draft; Publish promotes the draft. Sends always use the published content.

type TemplateCreateParams

type TemplateCreateParams struct {
	Name    string  `json:"name"`
	Subject *string `json:"subject,omitempty"`
	// Kind defaults to html and is immutable once the template exists.
	Kind     TemplateKind `json:"kind,omitempty"`
	HTML     *string      `json:"html,omitempty"`
	Markdown *string      `json:"markdown,omitempty"`
}

TemplateCreateParams is the body for TemplatesService.Create. The new template starts unpublished. For kind=html supply HTML; for kind=markdown supply Markdown. The plain-text body is always derived server-side.

type TemplateDraft

type TemplateDraft struct {
	Subject *string `json:"subject"`
	HTML    *string `json:"html"`
	// Text is always machine-derived from the draft's HTML/Markdown.
	Text      *string `json:"text"`
	Markdown  *string `json:"markdown"`
	UpdatedAt string  `json:"updated_at"`
}

TemplateDraft is the unpublished draft content for a template.

type TemplateDraftParams

type TemplateDraftParams struct {
	Subject  *string `json:"subject,omitempty"`
	HTML     *string `json:"html,omitempty"`
	Markdown *string `json:"markdown,omitempty"`
}

TemplateDraftParams is the body for TemplatesService.UpdateDraft. For kind=html supply HTML; for kind=markdown supply Markdown.

type TemplateDuplicateParams

type TemplateDuplicateParams struct {
	// Name for the copy. Defaults to "<source name> (copy)" when omitted.
	Name string `json:"name,omitempty"`
}

TemplateDuplicateParams is the body for TemplatesService.Duplicate.

type TemplateKind

type TemplateKind string

TemplateKind is a template's authoring format. Immutable once a template exists.

const (
	TemplateKindHTML     TemplateKind = "html"
	TemplateKindMarkdown TemplateKind = "markdown"
)

type TemplateUpdateParams

type TemplateUpdateParams struct {
	Name string `json:"name"`
}

TemplateUpdateParams is the body for TemplatesService.Update. Only Name is mutable; content is draft-versioned.

type TemplatesService

type TemplatesService struct {
	// contains filtered or unexported fields
}

TemplatesService holds the /templates operations, including the draft/publish flow. Access it via Client.Templates.

func (*TemplatesService) Create

func (s *TemplatesService) Create(ctx context.Context, params *TemplateCreateParams, opts ...RequestOption) (*Template, error)

Create makes a template. It starts unpublished — publish it before sending.

func (*TemplatesService) Delete

func (s *TemplatesService) Delete(ctx context.Context, id string, opts ...RequestOption) error

Delete permanently removes a template.

func (*TemplatesService) DeleteDraft

func (s *TemplatesService) DeleteDraft(ctx context.Context, id string, opts ...RequestOption) error

DeleteDraft discards the template's draft without touching published content.

func (*TemplatesService) Duplicate

func (s *TemplatesService) Duplicate(ctx context.Context, id string, params *TemplateDuplicateParams, opts ...RequestOption) (*Template, error)

Duplicate copies a template. The copy starts unpublished with a draft seeded from the source's current editable content. Pass nil params to accept the default name.

func (*TemplatesService) Get

func (s *TemplatesService) Get(ctx context.Context, id string, opts ...RequestOption) (*Template, error)

Get retrieves a template, including its published content.

func (*TemplatesService) GetDraft

func (s *TemplatesService) GetDraft(ctx context.Context, id string, opts ...RequestOption) (*TemplateDraft, error)

GetDraft retrieves the template's unpublished draft. It returns a not_found error if none exists.

func (*TemplatesService) List

func (s *TemplatesService) List(ctx context.Context, params ListParams, opts ...RequestOption) (*Page[Template], error)

List returns one page of the team's templates, newest-first.

func (*TemplatesService) Publish

func (s *TemplatesService) Publish(ctx context.Context, id string, opts ...RequestOption) (*Template, error)

Publish promotes the draft into the published slot, consuming the draft.

func (*TemplatesService) Update

func (s *TemplatesService) Update(ctx context.Context, id string, params *TemplateUpdateParams, opts ...RequestOption) (*Template, error)

Update changes a template's name. Body content lives on the draft.

func (*TemplatesService) UpdateDraft

func (s *TemplatesService) UpdateDraft(ctx context.Context, id string, params *TemplateDraftParams, opts ...RequestOption) (*TemplateDraft, error)

UpdateDraft creates or updates the template's draft. Idempotent upsert; published content is untouched.

type Tracking

type Tracking struct {
	// Opens injects the open-tracking pixel into the HTML body when non-nil.
	Opens *bool `json:"opens,omitempty"`
	// Clicks rewrites links for click tracking when non-nil.
	Clicks *bool `json:"clicks,omitempty"`
}

Tracking overrides the sending domain's open/click tracking defaults for one message. A nil field leaves that dimension at the domain default.

type Unsubscribe

type Unsubscribe struct {
	Mode UnsubscribeMode `json:"mode"`
	// DisplayName is the human-readable label rendered on the hosted
	// confirmation page.
	DisplayName string `json:"display_name,omitempty"`
}

Unsubscribe configures one-click unsubscribe headers for a send.

type UnsubscribeMode

type UnsubscribeMode string

UnsubscribeMode is the one-click unsubscribe behavior for a send.

const (
	// UnsubscribeGenerate mints a per-recipient signed token and injects RFC
	// 8058 unsubscribe headers. Requires a Topic on the send.
	UnsubscribeGenerate UnsubscribeMode = "generate"
	// UnsubscribeNone injects nothing — for transactional sends that must not
	// carry unsubscribe semantics.
	UnsubscribeNone UnsubscribeMode = "none"
)

type VerificationFailure

type VerificationFailure struct {
	// Code is a stable, switchable failure code.
	Code string `json:"code"`
	// Message is a human-readable description with record names interpolated.
	Message string `json:"message"`
}

VerificationFailure is a stable failure category plus a human-readable message.

type VerifyOption

type VerifyOption func(*verifyConfig)

VerifyOption configures webhook signature verification.

func WithNow

func WithNow(unixSeconds int64) VerifyOption

WithNow overrides the current time (Unix seconds) used for the freshness check. For tests.

func WithTolerance

func WithTolerance(d time.Duration) VerifyOption

WithTolerance overrides the maximum delivery age. A zero value disables the freshness check.

type Webhook

type Webhook struct {
	ID     string             `json:"id"`
	Name   string             `json:"name"`
	URL    string             `json:"url"`
	Events []WebhookEventType `json:"events"`
	Status WebhookStatus      `json:"status"`
	// SigningSecretPrefix is the first 12 characters of the signing secret.
	SigningSecretPrefix string `json:"signing_secret_prefix"`
	// SigningSecretPreviousPrefix is the prefix of the previous secret while a
	// rotation grace window is open, else nil.
	SigningSecretPreviousPrefix *string `json:"signing_secret_previous_prefix"`
	// SigningSecretGraceExpiresAt is when the rotation grace window ends, or nil.
	SigningSecretGraceExpiresAt *string `json:"signing_secret_grace_expires_at"`
	LastDeliveryAt              *string `json:"last_delivery_at"`
	CreatedAt                   string  `json:"created_at"`
}

Webhook is a webhook subscription. The signing secret is never returned here.

type WebhookCreateParams

type WebhookCreateParams struct {
	Name string `json:"name"`
	// URL is an https:// endpoint to receive signed deliveries.
	URL string `json:"url"`
	// Events is at least one event type to subscribe to.
	Events []WebhookEventType `json:"events"`
}

WebhookCreateParams is the body for WebhooksService.Create.

type WebhookDelivery

type WebhookDelivery struct {
	// BatchID identifies this batch. Stable across retries — de-duplicate on it.
	BatchID string `json:"batch_id"`
	// Timestamp is the Unix timestamp the batch was signed with.
	Timestamp int64                  `json:"timestamp"`
	Events    []WebhookDeliveryEvent `json:"events"`
}

WebhookDelivery is the outer envelope of a webhook delivery: one batch of one or more events.

func UnwrapWebhookEvent

func UnwrapWebhookEvent(payload []byte, signatureHeader, secret string, opts ...VerifyOption) (*WebhookDelivery, error)

UnwrapWebhookEvent verifies a delivery and returns its parsed body. It is a thin wrapper over VerifyWebhookSignature that unmarshals only after the signature checks out.

type WebhookDeliveryEvent

type WebhookDeliveryEvent struct {
	// ID is the unique event id. Stable across retries — de-duplicate on it.
	ID string `json:"id"`
	// Type is a WebhookEventType or "webhook.test".
	Type       string `json:"type"`
	OccurredAt string `json:"occurred_at"`
	// Data always carries email_id; the rest depends on the event type.
	Data map[string]any `json:"data"`
}

WebhookDeliveryEvent is one event inside a WebhookDelivery.

type WebhookEventType

type WebhookEventType string

WebhookEventType is an event type a webhook can subscribe to.

const (
	WebhookEventSent         WebhookEventType = "email.sent"
	WebhookEventDelivered    WebhookEventType = "email.delivered"
	WebhookEventDelayed      WebhookEventType = "email.delayed"
	WebhookEventBounced      WebhookEventType = "email.bounced"
	WebhookEventComplained   WebhookEventType = "email.complained"
	WebhookEventSuppressed   WebhookEventType = "email.suppressed"
	WebhookEventUnsubscribed WebhookEventType = "email.unsubscribed"
	WebhookEventOpened       WebhookEventType = "email.opened"
	WebhookEventClicked      WebhookEventType = "email.clicked"
)

type WebhookStatus

type WebhookStatus string

WebhookStatus is a webhook's delivery state. Only "active" and "disabled" can be set through the API; "circuit_disabled" is server-managed.

const (
	WebhookStatusActive          WebhookStatus = "active"
	WebhookStatusDisabled        WebhookStatus = "disabled"
	WebhookStatusCircuitDisabled WebhookStatus = "circuit_disabled"
)

type WebhookTestResult

type WebhookTestResult struct {
	// Delivered is true only when the endpoint returned a 2xx status.
	Delivered bool `json:"delivered"`
	// StatusCode is the HTTP status the endpoint returned, or nil on a network
	// failure.
	StatusCode *int `json:"status_code"`
	// LatencyMS is wall-clock time from request start to response or error.
	LatencyMS int `json:"latency_ms"`
	// Error is a human-readable failure reason, or nil on success.
	Error *string `json:"error"`
	// ResponseBodyPreview is a truncated preview of the endpoint's response body.
	ResponseBodyPreview *string `json:"response_body_preview"`
}

WebhookTestResult is the outcome of a synchronous test delivery. A bad endpoint never returns an error — read Delivered and StatusCode.

type WebhookUpdateParams

type WebhookUpdateParams struct {
	Name   string             `json:"name"`
	URL    string             `json:"url"`
	Events []WebhookEventType `json:"events"`
	// Status sets "disabled" to pause delivery or "active" to resume.
	Status WebhookStatus `json:"status"`
}

WebhookUpdateParams is the body for WebhooksService.Update.

type WebhookVerificationError

type WebhookVerificationError struct {
	// Reason is the machine-readable cause. Branch on this.
	Reason  WebhookVerificationReason
	Message string
}

WebhookVerificationError is returned when a webhook delivery's signature cannot be verified.

func (*WebhookVerificationError) Error

func (e *WebhookVerificationError) Error() string

type WebhookVerificationReason

type WebhookVerificationReason string

WebhookVerificationReason is the machine-readable cause of a signature verification failure. Branch on it rather than the message.

const (
	// ReasonMalformedHeader means the Anypost-Signature header could not be parsed.
	ReasonMalformedHeader WebhookVerificationReason = "malformed_header"
	// ReasonNoTimestamp means the header carried no t= component.
	ReasonNoTimestamp WebhookVerificationReason = "no_timestamp"
	// ReasonNoSignatures means the header carried no v1= component.
	ReasonNoSignatures WebhookVerificationReason = "no_signatures"
	// ReasonTimestampOutOfTolerance means the delivery is older than the tolerance.
	ReasonTimestampOutOfTolerance WebhookVerificationReason = "timestamp_out_of_tolerance"
	// ReasonNoMatch means no v1= component matched the computed signature.
	ReasonNoMatch WebhookVerificationReason = "no_match"
)

type WebhookWithSecret

type WebhookWithSecret struct {
	Webhook
	// SigningSecret is the full signing secret (whsec_...). Returned once; store
	// it securely.
	SigningSecret string `json:"signing_secret"`
}

WebhookWithSecret is a webhook with its full signing secret. Returned only on create and rotate-secret.

type WebhooksService

type WebhooksService struct {
	// contains filtered or unexported fields
}

WebhooksService holds the /webhooks operations. Access it via Client.Webhooks.

func (*WebhooksService) Create

Create makes a webhook. The full SigningSecret is on this response only — store it now to verify future deliveries; later reads return only the prefix.

func (*WebhooksService) Delete

func (s *WebhooksService) Delete(ctx context.Context, id string, opts ...RequestOption) error

Delete permanently removes a webhook.

func (*WebhooksService) Get

func (s *WebhooksService) Get(ctx context.Context, id string, opts ...RequestOption) (*Webhook, error)

Get retrieves a webhook. The signing secret is never returned — only its prefix.

func (*WebhooksService) List

func (s *WebhooksService) List(ctx context.Context, params ListParams, opts ...RequestOption) (*Page[Webhook], error)

List returns one page of the team's webhooks, newest-first.

func (*WebhooksService) RotateSecret

func (s *WebhooksService) RotateSecret(ctx context.Context, id string, opts ...RequestOption) (*WebhookWithSecret, error)

RotateSecret rotates the signing secret. The new secret is on this response only. The previous secret stays valid for a 24h grace window. Rotating again before the window ends returns a webhook_rotation_in_progress conflict.

func (*WebhooksService) Test

Test sends one synthetic webhook.test event and reports the outcome. One-shot, not retried, and absent from delivery history. It returns the result even when the endpoint fails — read Delivered and StatusCode. Works on a disabled webhook too.

func (*WebhooksService) Update

func (s *WebhooksService) Update(ctx context.Context, id string, params *WebhookUpdateParams, opts ...RequestOption) (*Webhook, error)

Update changes a webhook's name, URL, events, and status. It does not rotate the signing secret — use RotateSecret.

type WhoamiAPIKey

type WhoamiAPIKey struct {
	ID          string      `json:"id"`
	Permissions Permissions `json:"permissions"`
}

WhoamiAPIKey identifies the API key on the request.

type WhoamiLimits added in v1.4.0

type WhoamiLimits struct {
	// Daily is the messages the team may send per calendar day (UTC).
	// Exceeding it returns 429 with scope "daily".
	Daily int `json:"daily"`
	// Monthly is the messages the team may send per billing month. Exceeding
	// it returns 429 with scope "monthly", unless prepaid overage credits
	// cover the excess; those are not counted here.
	Monthly int `json:"monthly"`
	// DeliveryRatePerMinute is how fast accepted mail is released to receiving
	// servers. It is not a request limit and never a rejection: mail beyond
	// this rate queues and drains at the metered rate.
	DeliveryRatePerMinute int `json:"delivery_rate_per_minute"`
}

WhoamiLimits are the sending limits currently enforced against a team. These are effective values and can differ from the plan defaults, so read them at runtime rather than hardcoding them.

type WhoamiResponse

type WhoamiResponse struct {
	// Team is the team the key belongs to, or nil if it could not be resolved.
	Team *WhoamiTeam `json:"team"`
	// APIKey describes the key on the request.
	APIKey WhoamiAPIKey `json:"api_key"`
	// Limits are the sending limits in force for the team, or nil if the team
	// could not be resolved.
	Limits *WhoamiLimits `json:"limits"`
}

WhoamiResponse is the identity resolved from the request's API key.

type WhoamiTeam

type WhoamiTeam struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

WhoamiTeam identifies the team behind the API key.

Directories

Path Synopsis
examples
send command
Command send sends a single email.
Command send sends a single email.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL