sendclipper

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 23 Imported by: 0

README

SendClipper Go SDK

The official Go library for the SendClipper email API.

go get github.com/sendclipper/sendclipper-go

At a go.work root that is not a module itself, go get refuses: run it inside the module that sends mail (the directory with its own go.mod).

Go 1.23 or newer. No dependencies outside the standard library. API reference on pkg.go.dev.

Setup

Create an API key in the dashboard under API keys.

import "github.com/sendclipper/sendclipper-go"

client := sendclipper.NewClient("sc_live_xxxxxxxxxxxxxxxx")
// or: sendclipper.NewClient("")  reads SENDCLIPPER_API_KEY

A client is safe for concurrent use: make one and share it.

Send an email

email, err := client.Emails.Send(ctx, &sendclipper.SendEmailParams{
	From:    "hello@send.acme.io",
	To:      "ada@lumen.io",
	Subject: "Your receipt",
	HTML:    "<p>Thanks for your order.</p>",
	Text:    "Thanks for your order.",
})
if err != nil {
	var apiErr *sendclipper.Error
	if errors.As(err, &apiErr) {
		log.Println(apiErr.Name, apiErr.Message) // e.g. domain_not_verified
	}
	return err
}
fmt.Println("Email", email.ID, "accepted")

From must be a bare address on a domain you verified in SendClipper. Each message has one recipient in To; for several, use Batch.Send.

Send with a template

Design and publish a template in the dashboard, then send it by id or by its send name:

client.Emails.Send(ctx, &sendclipper.SendEmailParams{
	From:       "hello@send.acme.io",
	To:         "ada@lumen.io",
	TemplateID: "tpl_9f2b7c10ad42", // or Template: "welcome"
	Variables:  map[string]any{"first_name": "Ada"},
})

A tag with no value and no {{tag|fallback}} is refused by name (template_variables_missing), never sent blank.

Send a batch

Up to 500 messages in one request. Each item is accepted or refused on its own:

res, err := client.Batch.Send(ctx, []sendclipper.SendEmailParams{
	{From: "hello@send.acme.io", To: "ada@lumen.io", Subject: "Hi Ada", HTML: "<p>Hi</p>"},
	{From: "hello@send.acme.io", To: "alan@lumen.io", TemplateID: "tpl_9f2b7c10ad42", Variables: map[string]any{"first_name": "Alan"}},
})
for _, item := range res.Data {
	if item.Status == "failed" {
		log.Println(item.Index, item.Error.Code)
	}
}

Errors

Every method returns the decoded response or an error. A refusal from the API, or a request that never got an answer, is a *sendclipper.Error:

type Error struct {
	Name       string          // SendClipper's code: "validation_failed", "rate_limited", "network_error", ...
	Message    string          // a sentence for a person
	StatusCode int             // 0 when no answer arrived (network, timeout, cancelled context)
	Detail     json.RawMessage // code-specific facts
	Headers    http.Header     // the response headers, nil when no answer arrived
}

A network_error wraps the transport error, so errors.Is(err, context.DeadlineExceeded) works. For a successful call's headers, pass sendclipper.WithResponseHeaders(&h).

Retries and idempotency

Network errors, 429 and 5xx answers are retried twice with backoff, honouring Retry-After. Every JSON write carries an Idempotency-Key, and the API answers a repeated key with the first result for 24 hours. A retried send therefore never goes out twice.

To make your own retries safe as well (a job that runs again after a crash), pass a key that names the logical operation:

client.Emails.Send(ctx, params, sendclipper.WithIdempotencyKey("order-"+order.ID+"-receipt"))

Options

The context bounds the whole call, retries included. Per call:

Option Meaning
WithIdempotencyKey(key) Your own key for a write
WithRequestTimeout(d) Per-attempt timeout (default 30 s)
WithRequestMaxRetries(n) Retries for this call (default 2)
WithHeader(name, value) An extra request header
WithResponseHeaders(&h) Store the response headers

Client options: NewClient(key, WithBaseURL(url), WithHTTPClient(hc), WithTimeout(d), WithMaxRetries(n), WithDefaultHeader(name, value)). The base URL also reads SENDCLIPPER_BASE_URL.

Iterate

Lists that span pages have an All method that returns an iterator:

for email, err := range client.Emails.All(ctx, &sendclipper.ListEmailsParams{Status: sendclipper.StatusBounced}) {
	if err != nil {
		return err // a failed page is yielded once, then the loop ends
	}
	fmt.Println(email.To)
}

Verify webhooks

SendClipper signs each delivery. Verify it against the raw body, before decoding:

http.HandleFunc("/hooks/sendclipper", func(w http.ResponseWriter, r *http.Request) {
	payload, _ := io.ReadAll(r.Body) // the raw body, before any json.Decode
	event, err := sendclipper.VerifyWebhook(payload, r.Header, os.Getenv("SENDCLIPPER_WEBHOOK_SECRET"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	// event.ID is stable across retries: deduplicate on it
	log.Println(event.Event, event.Data.To)
})

A refusal is a *sendclipper.WebhookVerificationError whose Reason says which check failed. During a secret rotation a delivery carries two signatures and either one is accepted. A delivery signed more than five minutes away from now is refused. event.Data.Raw holds every field of the event's data. If you only hold the header's value, use VerifyWebhookSignature.

To test your handler locally, sendclipper.WebhookSignatureHeader(secret, rawBody, 0) builds a valid header.

Sandboxes

A sandbox traps mail while you develop: nothing is delivered.

box, _ := client.Sandboxes.Create(ctx, &sendclipper.SandboxParams{Name: "my-app-dev"})
creds, _ := client.Sandboxes.Credentials(ctx, box.ID) // SMTP host, port, username, password

// Or send through the sandbox's JSON door with its own token, using the same fields as Emails.Send:
token, _ := client.Sandboxes.ResetCredentials(ctx, box.ID)
client.Sandboxes.Send(ctx, token.APIToken, &sendclipper.SendEmailParams{From: "app@example.com", To: "test@example.com", Subject: "Hi", HTML: "<p>Hi</p>"})

// In a test: wait for the message to arrive (bound it with the context)
wctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
msg, err := client.Sandboxes.Messages.WaitFor(wctx, box.ID, &sendclipper.WaitForOptions{
	Match: func(m sendclipper.SandboxMessageSummary) bool { return m.Subject == "Hi" },
})

Everything else

Service Methods
Emails Send, Get, List, All, Stats
Batch Send
Domains List, Create, Records, Verify, EnableReceiving, DisableReceiving, SetRegion, DeletionPreview, Remove
Templates List, Get, HTML, Create, Update, Remove, Duplicate, Publish, Unpublish, Preview, SetTags, Projects
Templates.Tags List, Create, Update, Remove
Templates.Assets List, Upload
Sandboxes List, Get, Create, Update, Remove, Credentials, ResetCredentials, Send
Sandboxes.Messages List, Get, Raw, Remove, Clear, MarkAllRead, WaitFor
Webhooks List, Create, Update, Remove, RotateSecret, Deliveries, Verify
Webhooks.Dispatches List, All, Get, Replay, ReplayAll
Contacts List, All, Create, Import, ImportPreview, Export
Audiences List
Suppressions List, All, Create, Remove, Export
Mailboxes List, Get, Create, Update, Archive
Mailboxes.Messages List, All, Get, Attachment
Automations Enroll
Workspace Get
APIKeys List

Management routes need a key with Full access. A sending-only key can call Emails.Send and Batch.Send.

Full API reference: https://sendclipper.com/docs.

License

MIT

Documentation

Overview

Package sendclipper is the official Go client for the SendClipper email API.

client := sendclipper.NewClient("sc_live_...") // or NewClient(""): reads SENDCLIPPER_API_KEY
email, err := client.Emails.Send(ctx, &sendclipper.SendEmailParams{
	From:    "hello@send.acme.io",
	To:      "ada@lumen.io",
	Subject: "Your receipt",
	HTML:    "<p>Thanks for your order.</p>",
})
var apiErr *sendclipper.Error
if errors.As(err, &apiErr) {
	log.Println(apiErr.Name, apiErr.Message) // e.g. domain_not_verified
}

Every method takes a context and returns the decoded response or an error. A refusal from the API or a failed connection is always a *Error whose Name is SendClipper's machine-readable code ("validation_failed", "rate_limited", "network_error", ...).

Network errors, 429 and 5xx answers are retried with backoff, honouring Retry-After. Every JSON write carries an Idempotency-Key, and the API answers a repeated key with the first result for 24 hours, so a retried send never goes out twice.

Verify webhook deliveries with VerifyWebhook.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

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

func main() {
	client := sendclipper.NewClient("sc_live_xxxxxxxxxxxxxxxx")

	email, err := client.Emails.Send(context.Background(), &sendclipper.SendEmailParams{
		From:    "hello@send.acme.io",
		To:      "ada@lumen.io",
		Subject: "Your receipt",
		HTML:    "<p>Thanks for your order.</p>",
		Text:    "Thanks for your order.",
	})
	var apiErr *sendclipper.Error
	if errors.As(err, &apiErr) {
		log.Println(apiErr.Name, apiErr.Message) // e.g. domain_not_verified
		return
	}
	fmt.Println("Email", email.ID, "accepted")
}

Index

Examples

Constants

View Source
const DefaultBaseURL = "https://sendclipper.com"

DefaultBaseURL is where requests go unless WithBaseURL or SENDCLIPPER_BASE_URL says otherwise.

View Source
const SignatureHeader = "X-SendClipper-Signature-V2"

SignatureHeader is the header SendClipper signs deliveries in.

View Source
const Version = "0.1.1"

Version is this SDK's release. CI keeps it equal to the release tag.

View Source
const WebhookTolerance = 5 * time.Minute

WebhookTolerance is SendClipper's documented tolerance: five minutes.

Variables

This section is empty.

Functions

func Bool

func Bool(b bool) *bool

Bool returns a pointer to b, for the optional booleans in params (TrackOpens: sendclipper.Bool(false)).

func ParseSignatureHeader

func ParseSignatureHeader(header string) (timestamp int64, signatures []string, ok bool)

ParseSignatureHeader reads t=<seconds>,v1=<hex>[,v1=<hex>]. ok is false when there is no valid t=.

func SignPayload

func SignPayload(secret string, timestamp int64, rawBody []byte) string

SignPayload is hex(HMAC-SHA256(secret, "{timestamp}.{rawBody}")). Exported for tests of your own handler.

func WebhookSignatureHeader

func WebhookSignatureHeader(secret string, rawBody []byte, timestamp int64) string

WebhookSignatureHeader builds a valid header value for a body, e.g. to test your endpoint locally. A zero timestamp means now.

Types

type APIKey

type APIKey struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	Masked     string     `json:"masked"`
	Prefix     string     `json:"prefix"`
	Last4      string     `json:"last4"`
	Permission string     `json:"permission"` // full, sending or read
	DomainID   *string    `json:"domainId"`
	Domain     *string    `json:"domain"`
	CreatedAt  time.Time  `json:"createdAt"`
	LastUsedAt *time.Time `json:"lastUsedAt"`
}

APIKey is one of the workspace's keys, masked.

type APIKeysService

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

APIKeysService lists API keys. Keys are created and revoked in the dashboard only.

func (*APIKeysService) List

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

List returns the workspace's keys, masked.

type Audience

type Audience struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Color string `json:"color"`
	Count int    `json:"count"`
}

Audience is a list of contacts.

type AudienceList

type AudienceList struct {
	Audiences     []Audience `json:"audiences"`
	TotalContacts int        `json:"totalContacts"`
}

AudienceList is every audience.

type AudiencesService

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

AudiencesService lists audiences.

func (*AudiencesService) List

func (s *AudiencesService) List(ctx context.Context, opts ...RequestOption) (*AudienceList, error)

List returns every audience and the total contact count.

type AutomationsService

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

AutomationsService starts automations.

func (*AutomationsService) Enroll

func (s *AutomationsService) Enroll(ctx context.Context, automationID, email string, opts ...RequestOption) (map[string]any, error)

Enroll starts a Live automation with a manual trigger for one contact.

type BatchItemError

type BatchItemError struct {
	Code    string          `json:"code"`
	Message string          `json:"message"`
	Detail  json.RawMessage `json:"detail,omitempty"`
}

BatchItemError is why one batch item was refused.

type BatchResult

type BatchResult struct {
	Index     int             `json:"index"`
	Status    string          `json:"status"`
	ID        string          `json:"id,omitempty"`
	MessageID string          `json:"messageId,omitempty"`
	Error     *BatchItemError `json:"error,omitempty"`
}

BatchResult is one submitted item's outcome. Status is "accepted" (ID and MessageID set) or "failed" (Error set).

type BatchService

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

BatchService sends up to 500 messages in one request.

func (*BatchService) Send

func (s *BatchService) Send(ctx context.Context, emails []SendEmailParams, opts ...RequestOption) (*SendBatchResponse, error)

Send sends up to 500 messages (one recipient each). Every item is accepted or refused on its own; a refused item costs its siblings nothing.

type Client

type Client struct {
	Emails       *EmailsService
	Batch        *BatchService
	Domains      *DomainsService
	Templates    *TemplatesService
	Sandboxes    *SandboxesService
	Webhooks     *WebhooksService
	Contacts     *ContactsService
	Audiences    *AudiencesService
	Suppressions *SuppressionsService
	Mailboxes    *MailboxesService
	Automations  *AutomationsService
	Workspace    *WorkspaceService
	APIKeys      *APIKeysService
	// contains filtered or unexported fields
}

Client talks to the SendClipper API. Make one with NewClient and share it: it is safe for concurrent use.

func NewClient

func NewClient(apiKey string, opts ...ClientOption) *Client

NewClient makes a client. An empty apiKey reads SENDCLIPPER_API_KEY.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL is the API origin requests go to, without /v1.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client.

func WithBaseURL

func WithBaseURL(url string) ClientOption

WithBaseURL points the client at another API origin (no /v1). The default is SENDCLIPPER_BASE_URL, then https://sendclipper.com.

func WithDefaultHeader

func WithDefaultHeader(name, value string) ClientOption

WithDefaultHeader adds a header to every request.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) ClientOption

WithHTTPClient sends requests through your own *http.Client (a proxy, a custom transport). Its Timeout, if any, applies on top of the per-attempt one.

func WithMaxRetries

func WithMaxRetries(n int) ClientOption

WithMaxRetries sets how often a network error, 429 or 5xx is retried. Default 2.

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

WithTimeout sets the per-attempt timeout. Default 30 seconds.

type Contact

type Contact struct {
	ID            string    `json:"id"`
	Name          string    `json:"name"`
	Email         string    `json:"email"`
	Status        string    `json:"status"` // subscribed, unsubscribed or bounced
	AudienceID    *string   `json:"audienceId"`
	AudienceName  *string   `json:"audienceName"`
	AudienceColor *string   `json:"audienceColor"`
	CreatedAt     time.Time `json:"createdAt"`
}

Contact is one person in an audience.

type ContactList

type ContactList struct {
	Items      []Contact `json:"items"`
	Total      int       `json:"total"`
	Page       int       `json:"page"`
	PageSize   int       `json:"pageSize"`
	TotalPages int       `json:"totalPages"`
	TotalAll   int       `json:"totalAll"`
}

ContactList is one numbered page of contacts.

type ContactsService

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

ContactsService manages contacts.

func (*ContactsService) All

All walks every contact, page after page.

func (*ContactsService) Create

func (s *ContactsService) Create(ctx context.Context, params *CreateContactParams, opts ...RequestOption) (*Contact, error)

Create adds a contact.

func (*ContactsService) Export

func (s *ContactsService) Export(ctx context.Context, params *ExportContactsParams, opts ...RequestOption) (string, error)

Export returns every contact as CSV (re-importable) or vCard 4.0 (Format "vcf").

func (*ContactsService) Import

func (s *ContactsService) Import(ctx context.Context, params *ImportContactsParams, opts ...RequestOption) (map[string]any, error)

Import imports a CSV or vCard file. Importing never suppresses anyone.

func (*ContactsService) ImportPreview

func (s *ContactsService) ImportPreview(ctx context.Context, params *ImportContactsParams, opts ...RequestOption) (map[string]any, error)

ImportPreview says what an import would do, without importing.

func (*ContactsService) List

List returns one page of contacts.

type CreateContactParams

type CreateContactParams struct {
	Name       string `json:"name"`
	Email      string `json:"email"`
	AudienceID string `json:"audienceId,omitempty"`
	Status     string `json:"status,omitempty"`
}

CreateContactParams adds a contact.

type CreateDomainParams

type CreateDomainParams struct {
	// Name is a hostname such as send.acme.com. No scheme, no trailing slash.
	Name string `json:"name"`
	// Region cannot change once the domain holds messages.
	Region Region `json:"region"`
}

CreateDomainParams registers a domain.

type CreateMailboxParams

type CreateMailboxParams struct {
	DomainID    string `json:"domainId"`
	LocalPart   string `json:"localPart"`
	DisplayName string `json:"displayName,omitempty"`
	Color       string `json:"color,omitempty"`
	CatchAll    bool   `json:"catchAll,omitempty"`
}

CreateMailboxParams makes an address.

type CreateSuppressionParams

type CreateSuppressionParams struct {
	Email string `json:"email"`
	Note  string `json:"note,omitempty"`
}

CreateSuppressionParams suppresses an address (reason manual).

type CreateTemplateParams

type CreateTemplateParams struct {
	Name      string `json:"name,omitempty"`
	Subject   string `json:"subject,omitempty"`
	Preheader string `json:"preheader,omitempty"`
	// Blocks: see https://sendclipper.com/docs for the block types. Unknown fields are dropped.
	Blocks          []TemplateBlock `json:"blocks,omitempty"`
	ProjectDomainID string          `json:"projectDomainId,omitempty"`
	// Key is the send name, unique in its project: Emails.Send with Template: key.
	Key string `json:"key,omitempty"`
}

CreateTemplateParams makes a draft. Every field is optional.

type CreateWebhookParams

type CreateWebhookParams struct {
	URL    string             `json:"url"`
	Events []WebhookEventType `json:"events"`
	// AllowInsecureHTTP accepts a plain http:// URL. Off by default.
	AllowInsecureHTTP bool `json:"allowInsecureHttp,omitempty"`
}

CreateWebhookParams registers an endpoint.

type CursorInfo

type CursorInfo struct {
	Cursor  *string `json:"cursor"`
	HasMore bool    `json:"hasMore"`
}

CursorInfo is the paging block of a cursor-addressed list.

type DNSRecord

type DNSRecord struct {
	Type      string `json:"type"`
	Purpose   string `json:"purpose"`
	Host      string `json:"host"`
	Value     string `json:"value"`
	CopyValue string `json:"copyValue,omitempty"`
	Priority  int    `json:"priority,omitempty"`
	Status    string `json:"status,omitempty"`
}

DNSRecord is one record to publish.

type Domain

type Domain struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Region  Region `json:"region"`
	Status  string `json:"status"` // verified, pending or failed
	SpfOK   bool   `json:"spfOk"`
	DkimOK  bool   `json:"dkimOk"`
	DmarcOK bool   `json:"dmarcOk"`
	Records []struct {
		Name string `json:"name"`
		OK   bool   `json:"ok"`
	} `json:"records"`
	Sent              int        `json:"sent"`
	CheckCount        int        `json:"checkCount"`
	LastCheckedAt     *time.Time `json:"lastCheckedAt"`
	CreatedAt         time.Time  `json:"createdAt"`
	LastError         *string    `json:"lastError"`
	ReceivingEnabled  bool       `json:"receivingEnabled"`
	MxOK              bool       `json:"mxOk"`
	ReceivingVerified bool       `json:"receivingVerified"`
	ReceivingError    *string    `json:"receivingError"`
}

Domain is a sending (and optionally receiving) domain.

type DomainRecords

type DomainRecords struct {
	Domain  Domain      `json:"domain"`
	Records []DNSRecord `json:"records"`
}

DomainRecords is a domain with the DNS records to publish.

type DomainsService

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

DomainsService manages sending and receiving domains.

func (*DomainsService) Create

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

Create registers a sending domain. Next: Records, publish them, then Verify.

func (*DomainsService) DeletionPreview

func (s *DomainsService) DeletionPreview(ctx context.Context, id string, opts ...RequestOption) (map[string]any, error)

DeletionPreview says what deleting the domain would take with it.

func (*DomainsService) DisableReceiving

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

DisableReceiving stops receiving mail on the domain.

func (*DomainsService) EnableReceiving

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

EnableReceiving starts receiving mail on the domain. It adds receiving records and never blocks sending.

func (*DomainsService) List

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

List returns every domain.

func (*DomainsService) Records

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

Records returns the DNS records to publish, each with its current status.

func (*DomainsService) Remove

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

Remove deletes a domain.

func (*DomainsService) SetRegion

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

SetRegion moves a domain to another region. Refused (region_immutable) once it holds messages.

func (*DomainsService) Verify

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

Verify checks DNS now instead of waiting for the background check.

type DownloadedFile

type DownloadedFile struct {
	Content     []byte
	ContentType string
	Filename    string
}

DownloadedFile is what a download returns.

type EmailDetail

type EmailDetail struct {
	EmailRow
	HTML *string `json:"html"`
	Text *string `json:"text"`
	// BodyExpired is true once the retention window deleted the body.
	BodyExpired bool         `json:"bodyExpired"`
	Events      []EmailEvent `json:"events"`
	ThreadID    *string      `json:"threadId"`
	SendClass   SendClass    `json:"sendClass"`
	TrackOpens  *bool        `json:"trackOpens"`
	TrackClicks *bool        `json:"trackClicks"`
}

EmailDetail is one message with its body and timeline.

type EmailEvent

type EmailEvent struct {
	ID        string          `json:"id"`
	Type      string          `json:"type"`
	Meta      json.RawMessage `json:"meta"`
	CreatedAt time.Time       `json:"createdAt"`
}

EmailEvent is one step of a message's timeline.

type EmailList

type EmailList struct {
	Items    []EmailRow `json:"items"`
	Total    int        `json:"total"`
	Page     int        `json:"page"`
	PageSize int        `json:"pageSize"`
	// TotalCapped: a search counts only its newest 1,000 matches.
	TotalCapped bool `json:"totalCapped"`
}

EmailList is one numbered page of the log.

type EmailRow

type EmailRow struct {
	ID           string      `json:"id"`
	MessageID    string      `json:"messageId"`
	From         string      `json:"from"`
	To           string      `json:"to"`
	Domain       string      `json:"domain"`
	Subject      string      `json:"subject"`
	Status       EmailStatus `json:"status"`
	SesMessageID *string     `json:"sesMessageId"`
	CreatedAt    time.Time   `json:"createdAt"`
}

EmailRow is one message in the log.

type EmailStats

type EmailStats struct {
	Total             int `json:"total"`
	Queued            int `json:"queued"`
	Sent              int `json:"sent"`
	Delivered         int `json:"delivered"`
	Opened            int `json:"opened"`
	Clicked           int `json:"clicked"`
	Bounced           int `json:"bounced"`
	Complained        int `json:"complained"`
	Failed            int `json:"failed"`
	BodyRetentionDays int `json:"bodyRetentionDays"`
}

EmailStats counts messages per status.

type EmailStatus

type EmailStatus string

EmailStatus is a message's delivery state.

const (
	StatusQueued     EmailStatus = "queued"
	StatusSent       EmailStatus = "sent"
	StatusDelivered  EmailStatus = "delivered"
	StatusOpened     EmailStatus = "opened"
	StatusClicked    EmailStatus = "clicked"
	StatusBounced    EmailStatus = "bounced"
	StatusComplained EmailStatus = "complained"
	StatusFailed     EmailStatus = "failed"
)

type EmailsService

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

EmailsService sends mail and reads the sent-mail log.

func (*EmailsService) All

All walks every message matching params, page after page:

for email, err := range client.Emails.All(ctx, nil) { ... }

A failed page yields its error once and stops.

Example
package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	client := sendclipper.NewClient("")
	for email, err := range client.Emails.All(context.Background(), &sendclipper.ListEmailsParams{Status: sendclipper.StatusBounced}) {
		if err != nil {
			log.Println(err)
			break
		}
		fmt.Println(email.To)
	}
}

func (*EmailsService) Get

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

Get returns one message with its body and event timeline.

func (*EmailsService) List

func (s *EmailsService) List(ctx context.Context, params *ListEmailsParams, opts ...RequestOption) (*EmailList, error)

List returns one page of the sent-mail log, newest first.

func (*EmailsService) Send

func (s *EmailsService) Send(ctx context.Context, params *SendEmailParams, opts ...RequestOption) (*SentEmail, error)

Send sends one message. It returns once SendClipper accepted it (status queued); delivery is reported later through Get and webhooks.

Every call carries an Idempotency-Key, so the SDK's own retries never send twice. Pass WithIdempotencyKey to make YOUR retries safe too.

Example (Template)
package main

import (
	"context"
	"log"

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

func main() {
	client := sendclipper.NewClient("")
	_, err := client.Emails.Send(context.Background(), &sendclipper.SendEmailParams{
		From:       "hello@send.acme.io",
		To:         "ada@lumen.io",
		TemplateID: "tpl_9f2b7c10ad42", // or Template: "welcome"
		Variables:  map[string]any{"first_name": "Ada"},
	}, sendclipper.WithIdempotencyKey("order-42-receipt"))
	if err != nil {
		log.Println(err)
	}
}

func (*EmailsService) SendBatch

func (s *EmailsService) SendBatch(ctx context.Context, emails []SendEmailParams, opts ...RequestOption) (*SendBatchResponse, error)

SendBatch is the same as Batch.Send.

func (*EmailsService) Stats

func (s *EmailsService) Stats(ctx context.Context, opts ...RequestOption) (*EmailStats, error)

Stats counts messages per status.

type Error

type Error struct {
	// Name is the API's code: "domain_not_verified", "template_variables_missing",
	// "validation_failed", "rate_limited", or "network_error" when no answer arrived.
	Name string
	// Message is a sentence meant for a person.
	Message string
	// StatusCode is the HTTP status, or 0 when no answer arrived.
	StatusCode int
	// Detail holds code-specific facts (e.g. the missing template tags), as JSON.
	Detail json.RawMessage
	// Headers are the response headers, or nil when no answer arrived.
	Headers http.Header
	// contains filtered or unexported fields
}

Error is what every failed call returns: an API refusal, or a request that never got an answer. Branch on Name, SendClipper's machine-readable code; Message is a sentence meant for a person.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the transport error behind a "network_error".

type ExportContactsParams

type ExportContactsParams struct {
	Format   string // csv (default) or vcf
	Audience string
}

ExportContactsParams picks the export format.

type ImportContactsParams

type ImportContactsParams struct {
	// File is the CSV or vCard contents.
	File []byte
	// Filename (contacts.csv / contacts.vcf): the extension decides the parser.
	Filename        string
	AudienceID      string
	NewAudience     string
	CreateAudiences *bool
	// OnDuplicate is "skip" or "update".
	OnDuplicate string
	// Mapping is the column mapping as ImportPreview returns it.
	Mapping map[string]any
}

ImportContactsParams is a CSV or vCard import.

type ListContactsParams

type ListContactsParams struct {
	AudienceID string
	Status     string
	Page       int
	PageSize   int // 1-200, default 25
}

ListContactsParams filters contacts.

type ListEmailsParams

type ListEmailsParams struct {
	Search      string
	Status      EmailStatus
	Domain      string // exact sending domain
	BroadcastID string
	From        string // ISO 8601 lower bound
	To          string // ISO 8601 upper bound
	Page        int
	PageSize    int // 1-100, default 50
}

ListEmailsParams filters the sent-mail log.

type ListMailboxMessagesParams

type ListMailboxMessagesParams struct {
	Folder string // all, inbox, starred, sent, drafts, archive, spam, trash
	Search string
	Label  string
	Limit  int // 1-200
	Cursor string
}

ListMailboxMessagesParams filters an address's mail.

type ListSandboxMessagesParams

type ListSandboxMessagesParams struct {
	Search string
	Limit  int
}

ListSandboxMessagesParams filters a sandbox's messages.

type ListSuppressionsParams

type ListSuppressionsParams struct {
	Q      string // address search
	Reason string
	Cursor string
	Limit  int // 1-100
}

ListSuppressionsParams filters the list.

type ListTemplatesParams

type ListTemplatesParams struct {
	Tag     string // a tag id
	Project string // a project (domain) id
}

ListTemplatesParams filters the template list.

type ListWebhookDeliveriesParams

type ListWebhookDeliveriesParams struct {
	EndpointID string
	FinalOnly  *bool
	Limit      int
}

ListWebhookDeliveriesParams filters delivery attempts.

type ListWebhookDispatchesParams

type ListWebhookDispatchesParams struct {
	// State defaults to dead_lettered; "retrying" = pending or delivering; "all".
	State      string
	EndpointID string
	Cursor     string
	Limit      int // 1-100
}

ListWebhookDispatchesParams filters undelivered events.

type Mailbox

type Mailbox struct {
	ID          string `json:"id"`
	Address     string `json:"address"`
	DisplayName string `json:"displayName"`
	Domain      string `json:"domain"`
	DomainID    string `json:"domainId"`
	CatchAll    bool   `json:"catchAll"`
	Archived    bool   `json:"archived"`
	CanReceive  bool   `json:"canReceive"`
	CanSend     bool   `json:"canSend"`
	Unread      int    `json:"unread"`
	Total       int    `json:"total"`
}

Mailbox is one address on a receiving domain.

type MailboxMessage

type MailboxMessage struct {
	MailboxMessageRow
	BodyText      *string    `json:"bodyText"`
	BodyHTML      *string    `json:"bodyHtml"`
	BodyExpiredAt *time.Time `json:"bodyExpiredAt"`
	Thread        *struct {
		ID           string          `json:"id"`
		Subject      string          `json:"subject"`
		MessageCount int             `json:"messageCount"`
		LastAt       time.Time       `json:"lastAt"`
		Participants []string        `json:"participants"`
		Messages     json.RawMessage `json:"messages"`
	} `json:"thread"`
	// Attachments: download one with Mailboxes.Messages.Attachment.
	Attachments []struct {
		ID          string `json:"id"`
		Filename    string `json:"filename"`
		ContentType string `json:"contentType"`
		SizeBytes   int    `json:"sizeBytes"`
	} `json:"attachments"`
	// Authentication holds SPF/DKIM/DMARC/spam verdicts for received mail; nil for your own sends.
	Authentication map[string]any `json:"authentication"`
}

MailboxMessage is one message in full, with its conversation.

type MailboxMessageList

type MailboxMessageList struct {
	Items       []MailboxMessageRow `json:"items"`
	Total       int                 `json:"total"`
	Unread      int                 `json:"unread"`
	TotalCapped bool                `json:"totalCapped"`
	Page        CursorInfo          `json:"page"`
}

MailboxMessageList is one cursor page of a mailbox.

type MailboxMessageRow

type MailboxMessageRow struct {
	ID         string    `json:"id"`
	Folder     string    `json:"folder"`
	From       string    `json:"from"`
	FromName   string    `json:"fromName"`
	To         string    `json:"to"`
	Cc         *string   `json:"cc"`
	Bcc        *string   `json:"bcc"`
	Subject    string    `json:"subject"`
	Snippet    string    `json:"snippet"`
	Label      *string   `json:"label"`
	Unread     bool      `json:"unread"`
	Starred    bool      `json:"starred"`
	ReceivedAt time.Time `json:"receivedAt"`
	MailboxID  *string   `json:"mailboxId"`
	ThreadID   *string   `json:"threadId"`
}

MailboxMessageRow is one message in a mailbox list.

type MailboxMessagesService

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

MailboxMessagesService reads mail filed under an address. Reading never marks anything read.

func (*MailboxMessagesService) All

All walks an address's mail, following the cursor.

func (*MailboxMessagesService) Attachment

func (s *MailboxMessagesService) Attachment(ctx context.Context, mailboxID, messageID, attachmentID string, opts ...RequestOption) (*DownloadedFile, error)

Attachment downloads one attachment.

func (*MailboxMessagesService) Get

func (s *MailboxMessagesService) Get(ctx context.Context, mailboxID, messageID string, opts ...RequestOption) (*MailboxMessage, error)

Get returns one message in full, with its conversation.

func (*MailboxMessagesService) List

List returns one cursor page of an address's mail.

type MailboxesService

type MailboxesService struct {
	Messages *MailboxMessagesService
	// contains filtered or unexported fields
}

MailboxesService manages addresses on receiving domains.

func (*MailboxesService) Archive

func (s *MailboxesService) Archive(ctx context.Context, id string, opts ...RequestOption) (*Mailbox, error)

Archive archives the address. Its mail is kept.

func (*MailboxesService) Create

func (s *MailboxesService) Create(ctx context.Context, params *CreateMailboxParams, opts ...RequestOption) (*Mailbox, error)

Create makes a new address on a receiving domain.

func (*MailboxesService) Get

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

Get returns one address.

func (*MailboxesService) List

func (s *MailboxesService) List(ctx context.Context, includeArchived bool, opts ...RequestOption) ([]Mailbox, error)

List returns the addresses; includeArchived lists archived ones too.

func (*MailboxesService) Update

func (s *MailboxesService) Update(ctx context.Context, id string, params *UpdateMailboxParams, opts ...RequestOption) (*Mailbox, error)

Update changes the fields set in params.

type NamedRef

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

NamedRef is a {id, name} pair.

type Region

type Region string

Region is where Amazon SES registers a domain.

const (
	USEast1 Region = "us-east-1"
	EUWest1 Region = "eu-west-1"
	USWest2 Region = "us-west-2"
)

type ReplayAllParams

type ReplayAllParams struct {
	State      string `json:"state,omitempty"`
	EndpointID string `json:"endpointId,omitempty"`
}

ReplayAllParams scopes a bulk replay.

type ReplayAllResult

type ReplayAllResult struct {
	Queued    int  `json:"queued"`
	Remaining int  `json:"remaining"`
	Capped    bool `json:"capped"`
	Max       int  `json:"max"`
}

ReplayAllResult says what a bulk replay queued.

type RequestOption

type RequestOption func(*requestConfig)

RequestOption adjusts one call.

func WithHeader

func WithHeader(name, value string) RequestOption

WithHeader adds a header to one call.

func WithIdempotencyKey

func WithIdempotencyKey(key string) RequestOption

WithIdempotencyKey sets your own Idempotency-Key, so that YOUR retries of the same logical operation (a job that runs again after a crash) are safe too. Without it every write still gets a fresh key of its own.

func WithRequestMaxRetries

func WithRequestMaxRetries(n int) RequestOption

WithRequestMaxRetries overrides the client's retry count for one call.

func WithRequestTimeout

func WithRequestTimeout(d time.Duration) RequestOption

WithRequestTimeout overrides the client's per-attempt timeout for one call.

func WithResponseHeaders

func WithResponseHeaders(dst *http.Header) RequestOption

WithResponseHeaders stores the response's headers in *dst when the call succeeds (on failure they are on the *Error).

type ResetSandboxCredentials

type ResetSandboxCredentials struct {
	Username string `json:"username"`
	Password string `json:"password"`
	APIToken string `json:"apiToken"`
}

ResetSandboxCredentials holds the new credentials. The token is shown once.

type Sandbox

type Sandbox struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug"`
	// InboxAddress: mail sent to this address lands in the sandbox.
	InboxAddress  string     `json:"inboxAddress"`
	InboundLive   bool       `json:"inboundLive"`
	ProjectLabel  string     `json:"projectLabel"`
	Color         string     `json:"color"`
	Total         int        `json:"total"`
	Unread        int        `json:"unread"`
	MessageCap    int        `json:"messageCap"`
	RetentionDays int        `json:"retentionDays"`
	LastTrappedAt *time.Time `json:"lastTrappedAt"`
	CreatedAt     time.Time  `json:"createdAt"`
}

Sandbox traps mail: nothing sent to it is delivered.

type SandboxCredentials

type SandboxCredentials struct {
	Host            string `json:"host"`
	Port            int    `json:"port"`
	TLS             string `json:"tls"` // required, optional or none
	MaxRecipients   int    `json:"maxRecipients"`
	Username        string `json:"username"`
	Password        string `json:"password"`
	MaxMessageBytes int    `json:"maxMessageBytes"`
	RetentionDays   int    `json:"retentionDays"`
	APIEndpoint     string `json:"apiEndpoint"`
	InboxID         string `json:"inboxId"`
	InboxAddress    string `json:"inboxAddress"`
	InboundDomain   string `json:"inboundDomain"`
	InboundLive     bool   `json:"inboundLive"`
	// APITokenHint: the JSON token is hashed; only its hint can be read back.
	APITokenHint string `json:"apiTokenHint"`
}

SandboxCredentials point a project at the sandbox.

type SandboxMessage

type SandboxMessage struct {
	SandboxMessageSummary
	CcAddresses []string `json:"ccAddresses"`
	// EnvelopeFrom/EnvelopeTo are the SMTP envelope, recorded apart from the
	// headers (a Bcc recipient is only here).
	EnvelopeFrom string   `json:"envelopeFrom"`
	EnvelopeTo   []string `json:"envelopeTo"`
	BodyHTML     *string  `json:"bodyHtml"`
	// BodyText is nil when the message carried no text/plain part.
	BodyText *string `json:"bodyText"`
	Headers  []struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	} `json:"headers"`
	Transport string  `json:"transport"`
	ClientIP  *string `json:"clientIp"`
	HasRaw    bool    `json:"hasRaw"`
}

SandboxMessage is one trapped message in full.

type SandboxMessageSummary

type SandboxMessageSummary struct {
	ID          string    `json:"id"`
	Subject     string    `json:"subject"`
	FromAddress string    `json:"fromAddress"`
	FromName    string    `json:"fromName"`
	ToAddresses []string  `json:"toAddresses"`
	Snippet     string    `json:"snippet"`
	SizeBytes   int       `json:"sizeBytes"`
	Read        bool      `json:"read"`
	TrappedAt   time.Time `json:"trappedAt"`
}

SandboxMessageSummary is one trapped message in a list.

type SandboxMessagesService

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

SandboxMessagesService reads what a sandbox trapped.

func (*SandboxMessagesService) Clear

func (s *SandboxMessagesService) Clear(ctx context.Context, sandboxID string, opts ...RequestOption) (int, error)

Clear empties the sandbox and returns how many messages went.

func (*SandboxMessagesService) Get

func (s *SandboxMessagesService) Get(ctx context.Context, sandboxID, messageID string, opts ...RequestOption) (*SandboxMessage, error)

Get returns one trapped message in full.

func (*SandboxMessagesService) List

List returns a sandbox's messages, newest first.

func (*SandboxMessagesService) MarkAllRead

func (s *SandboxMessagesService) MarkAllRead(ctx context.Context, sandboxID string, opts ...RequestOption) (int, error)

MarkAllRead marks every message read and returns how many changed.

func (*SandboxMessagesService) Raw

func (s *SandboxMessagesService) Raw(ctx context.Context, sandboxID, messageID string, opts ...RequestOption) (string, error)

Raw returns the message's raw MIME source.

func (*SandboxMessagesService) Remove

func (s *SandboxMessagesService) Remove(ctx context.Context, sandboxID, messageID string, opts ...RequestOption) error

Remove deletes one trapped message.

func (*SandboxMessagesService) WaitFor

func (s *SandboxMessagesService) WaitFor(ctx context.Context, sandboxID string, opts *WaitForOptions) (*SandboxMessage, error)

WaitFor polls until a message arrives that opts.Match accepts, then returns it in full. Handy in end-to-end tests. Bound it with ctx: when ctx ends first the error's Name is "timeout".

type SandboxParams

type SandboxParams struct {
	Name         string `json:"name,omitempty"`
	ProjectLabel string `json:"projectLabel,omitempty"`
	Color        string `json:"color,omitempty"` // #rrggbb
}

SandboxParams creates or renames a sandbox.

type SandboxSendResult

type SandboxSendResult struct {
	ID        string `json:"id"`
	Duplicate bool   `json:"duplicate,omitempty"`
}

SandboxSendResult is what the sandbox JSON door answers.

type SandboxesService

type SandboxesService struct {
	Messages *SandboxMessagesService
	// contains filtered or unexported fields
}

SandboxesService manages sandboxes: inboxes that trap mail while you develop.

func (*SandboxesService) Create

func (s *SandboxesService) Create(ctx context.Context, params *SandboxParams, opts ...RequestOption) (*Sandbox, error)

Create makes a sandbox. Name is required.

func (*SandboxesService) Credentials

func (s *SandboxesService) Credentials(ctx context.Context, id string, opts ...RequestOption) (*SandboxCredentials, error)

Credentials returns the SMTP host, port, username and password to point a project at.

func (*SandboxesService) Get

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

Get returns one sandbox.

func (*SandboxesService) List

func (s *SandboxesService) List(ctx context.Context, opts ...RequestOption) ([]Sandbox, error)

List returns every sandbox.

func (*SandboxesService) Remove

func (s *SandboxesService) Remove(ctx context.Context, id string, opts ...RequestOption) (int, error)

Remove deletes the sandbox and every message in it, and returns how many messages went.

func (*SandboxesService) ResetCredentials

func (s *SandboxesService) ResetCredentials(ctx context.Context, id string, opts ...RequestOption) (*ResetSandboxCredentials, error)

ResetCredentials replaces BOTH the SMTP password and the sc_sbx_ token. The token is shown only here.

func (*SandboxesService) Send

func (s *SandboxesService) Send(ctx context.Context, sandboxToken string, params *SendEmailParams, opts ...RequestOption) (*SandboxSendResult, error)

Send sends to a sandbox through its JSON door, with the sandbox's own sc_sbx_ token. Same fields as Emails.Send; nothing is delivered.

func (*SandboxesService) Update

func (s *SandboxesService) Update(ctx context.Context, id string, params *SandboxParams, opts ...RequestOption) (*Sandbox, error)

Update renames a sandbox. Renaming never changes the address or the credentials.

type SendBatchResponse

type SendBatchResponse struct {
	Data    []BatchResult `json:"data"`
	Summary struct {
		Submitted int `json:"submitted"`
		Accepted  int `json:"accepted"`
		Failed    int `json:"failed"`
	} `json:"summary"`
}

SendBatchResponse holds one result per submitted item, in order.

type SendClass

type SendClass string

SendClass is "transactional" (the default) or "marketing".

const (
	Transactional SendClass = "transactional"
	Marketing     SendClass = "marketing"
)

type SendEmailParams

type SendEmailParams struct {
	// From is a bare address on a verified domain: "hello@send.acme.io". A display-name form is refused.
	From string `json:"from"`
	// To is ONE recipient. For several, use Batch.Send with one item each.
	To      string `json:"to"`
	Subject string `json:"subject,omitempty"`
	HTML    string `json:"html,omitempty"`
	// Text is the plain-text alternative.
	Text string `json:"text,omitempty"`
	// TemplateID is a published template's public id (tpl_...) or uuid.
	TemplateID string `json:"templateId,omitempty"`
	// Template is a published template's send name (its key, e.g. "welcome"). Use this or TemplateID.
	Template string `json:"template,omitempty"`
	// Variables fill {{tags}} in a template, or in a hand-written HTML body.
	Variables map[string]any `json:"variables,omitempty"`
	// SendClass "marketing" adds one-click unsubscribe and the postal-address footer.
	SendClass SendClass `json:"sendClass,omitempty"`
	// TrackOpens false leaves the open-tracking pixel out. Default true.
	TrackOpens *bool `json:"trackOpens,omitempty"`
	// TrackClicks false leaves links unwrapped. Default true.
	TrackClicks *bool `json:"trackClicks,omitempty"`
}

SendEmailParams is one message. Give it a body (Subject + HTML) or a published template (TemplateID or Template, plus Variables).

type SentEmail

type SentEmail struct {
	// ID is SendClipper's id for the message. Use it with Emails.Get.
	ID           string      `json:"id"`
	MessageID    string      `json:"messageId"`
	SesMessageID *string     `json:"sesMessageId"`
	From         string      `json:"from"`
	To           string      `json:"to"`
	Domain       string      `json:"domain"`
	Subject      string      `json:"subject"`
	Status       EmailStatus `json:"status"`
	CreatedAt    time.Time   `json:"createdAt"`
	TemplateID   *string     `json:"templateId"`
}

SentEmail is what Emails.Send answers: the message was accepted (queued).

type Suppression

type Suppression struct {
	ID              string    `json:"id"`
	Email           string    `json:"email"`
	Reason          string    `json:"reason"` // hard_bounce, soft_bounce, complaint, unsubscribe, manual, provider
	ReasonLabel     string    `json:"reasonLabel"`
	ReasonDetail    *string   `json:"reasonDetail"`
	DiagnosticCode  *string   `json:"diagnosticCode"`
	Scope           string    `json:"scope"` // marketing for an unsubscribe, all for everything else
	SourceEmailID   *string   `json:"sourceEmailId"`
	SourceMessageID *string   `json:"sourceMessageId"`
	CreatedAt       time.Time `json:"createdAt"`
}

Suppression stops mail to one address.

type SuppressionCreated

type SuppressionCreated struct {
	Created bool         `json:"created"`
	Row     *Suppression `json:"row"`
}

SuppressionCreated: Created is false when the address was already suppressed; Row is the entry either way.

type SuppressionList

type SuppressionList struct {
	Data   []Suppression `json:"data"`
	Page   CursorInfo    `json:"page"`
	Counts struct {
		Total    int            `json:"total"`
		ByReason map[string]int `json:"byReason"`
	} `json:"counts"`
}

SuppressionList is one cursor page of the list.

type SuppressionsService

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

SuppressionsService manages the suppression list.

func (*SuppressionsService) All

All walks every suppression matching params, following the cursor.

func (*SuppressionsService) Create

Create stops all mail to an address (reason manual).

func (*SuppressionsService) Export

Export returns the list as CSV. Cursor and Limit are ignored.

func (*SuppressionsService) List

List returns one cursor page of the list.

func (*SuppressionsService) Remove

func (s *SuppressionsService) Remove(ctx context.Context, id, reason string, opts ...RequestOption) error

Remove lifts a suppression. reason (10+ characters) is recorded. Complaint and provider suppressions cannot be removed (suppression_not_removable).

type Template

type Template struct {
	TemplateSummary
	Subject   string          `json:"subject"`
	Preheader string          `json:"preheader"`
	Blocks    []TemplateBlock `json:"blocks"`
	HTML      string          `json:"html"`
}

Template is a template with its body.

type TemplateAsset

type TemplateAsset struct {
	ID       string `json:"id"`
	PublicID string `json:"publicId"`
	// URL is public: a mail client can load it.
	URL       string    `json:"url"`
	MimeType  string    `json:"mimeType"`
	Bytes     int       `json:"bytes"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"createdAt"`
}

TemplateAsset is an uploaded image.

type TemplateAssetsService

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

TemplateAssetsService manages images templates can show.

func (*TemplateAssetsService) List

List returns the uploaded images.

func (*TemplateAssetsService) Upload

Upload stores an image and returns its public URL.

type TemplateBlock

type TemplateBlock = map[string]any

TemplateBlock is one block: {"type": "text", "text": "Hi {{first_name|there}}"}.

type TemplatePreview

type TemplatePreview struct {
	Subject   string `json:"subject"`
	Preheader string `json:"preheader"`
	HTML      string `json:"html"`
	Text      string `json:"text"`
	Variables []struct {
		Name     string  `json:"name"`
		Fallback *string `json:"fallback"`
		Value    string  `json:"value"`
		Provided bool    `json:"provided"`
	} `json:"variables"`
	// UnsafeLinks are links whose target a tag would make unsafe.
	UnsafeLinks []string `json:"unsafeLinks"`
}

TemplatePreview is a render that sent nothing.

type TemplateSummary

type TemplateSummary struct {
	ID          string        `json:"id"`
	PublicID    string        `json:"publicId"`
	Name        string        `json:"name"`
	Status      string        `json:"status"` // draft or published
	BlockCount  int           `json:"blockCount"`
	UpdatedAt   time.Time     `json:"updatedAt"`
	PublishedAt *time.Time    `json:"publishedAt"`
	CreatedAt   time.Time     `json:"createdAt"`
	Domains     []NamedRef    `json:"domains"`
	Tags        []TemplateTag `json:"tags"`
	Key         *string       `json:"key"`
	Project     *NamedRef     `json:"project"`
}

TemplateSummary is one card in the template list.

type TemplateTag

type TemplateTag struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Color         string `json:"color"`
	TemplateCount *int   `json:"templateCount,omitempty"`
}

TemplateTag labels templates.

type TemplateTagsService

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

TemplateTagsService manages template tags.

func (*TemplateTagsService) Create

func (s *TemplateTagsService) Create(ctx context.Context, name, color string, opts ...RequestOption) (*TemplateTag, error)

Create makes a tag. color is gray, red, orange, amber, green, teal, blue, purple or pink.

func (*TemplateTagsService) List

List returns every tag, each with its TemplateCount.

func (*TemplateTagsService) Remove

func (s *TemplateTagsService) Remove(ctx context.Context, id string, opts ...RequestOption) (int, error)

Remove deletes a tag and returns how many templates it was taken off.

func (*TemplateTagsService) Update

func (s *TemplateTagsService) Update(ctx context.Context, id, name, color string, opts ...RequestOption) (*TemplateTag, error)

Update renames or recolours a tag; an empty string leaves that field alone.

type TemplatesService

type TemplatesService struct {
	Tags   *TemplateTagsService
	Assets *TemplateAssetsService
	// contains filtered or unexported fields
}

TemplatesService manages email templates.

func (*TemplatesService) Create

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

Create makes a new draft. Publish it before sending with it. params may be nil.

func (*TemplatesService) Duplicate

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

Duplicate copies a template as a new draft.

func (*TemplatesService) Get

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

Get returns a template by uuid or public id (tpl_...).

func (*TemplatesService) HTML

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

HTML returns the rendered HTML, tags left in place.

func (*TemplatesService) List

List returns the templates, optionally filtered.

func (*TemplatesService) Preview

func (s *TemplatesService) Preview(ctx context.Context, id string, variables map[string]any, opts ...RequestOption) (*TemplatePreview, error)

Preview renders with variables without sending anything. variables may be nil.

func (*TemplatesService) Projects

func (s *TemplatesService) Projects(ctx context.Context, opts ...RequestOption) ([]NamedRef, error)

Projects returns the projects (domains) templates can belong to.

func (*TemplatesService) Publish

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

Publish makes a template sendable.

func (*TemplatesService) Remove

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

Remove deletes a template.

func (*TemplatesService) SetTags

func (s *TemplatesService) SetTags(ctx context.Context, id string, tagIDs []string, opts ...RequestOption) ([]TemplateTag, error)

SetTags replaces the template's tags with these tag ids.

func (*TemplatesService) Unpublish

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

Unpublish returns a template to draft.

func (*TemplatesService) Update

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

Update changes the fields set in params.

type UpdateMailboxParams

type UpdateMailboxParams struct {
	LocalPart    string   `json:"localPart,omitempty"`
	DisplayName  *string  `json:"displayName,omitempty"`
	Color        string   `json:"color,omitempty"`
	CatchAll     *bool    `json:"catchAll,omitempty"`
	Archived     *bool    `json:"archived,omitempty"`
	NotifyOwner  *bool    `json:"notifyOwner,omitempty"`
	EmbedOrigins []string `json:"embedOrigins,omitempty"`
}

UpdateMailboxParams changes the fields you set.

type UpdateTemplateParams

type UpdateTemplateParams struct {
	Name            *string         `json:"name,omitempty"`
	Subject         *string         `json:"subject,omitempty"`
	Preheader       *string         `json:"preheader,omitempty"`
	Blocks          []TemplateBlock `json:"blocks,omitempty"`
	DomainIDs       []string        `json:"domainIds,omitempty"`
	ProjectDomainID *string         `json:"projectDomainId,omitempty"`
	Key             *string         `json:"key,omitempty"`
}

UpdateTemplateParams changes the fields you set. Use a pointer to "" (or an empty slice) to clear one.

type UpdateWebhookParams

type UpdateWebhookParams struct {
	URL               string             `json:"url,omitempty"`
	Events            []WebhookEventType `json:"events,omitempty"`
	Active            *bool              `json:"active,omitempty"`
	AllowInsecureHTTP *bool              `json:"allowInsecureHttp,omitempty"`
}

UpdateWebhookParams changes the fields you set.

type UploadTemplateAssetParams

type UploadTemplateAssetParams struct {
	// MimeType is image/png, image/jpeg, image/gif or image/webp.
	MimeType string
	// Data is the file's bytes; it is base64-encoded for you.
	Data []byte
	Name string
}

UploadTemplateAssetParams is one image.

type VerifyOption

type VerifyOption func(*verifyConfig)

VerifyOption tunes VerifyWebhook.

func WithNow

func WithNow(t time.Time) VerifyOption

WithNow sets the current time, for tests.

func WithTolerance

func WithTolerance(d time.Duration) VerifyOption

WithTolerance replaces the five-minute tolerance.

type WaitForOptions

type WaitForOptions struct {
	// Match picks the message to wait for. Default: any message trapped after the call started.
	Match func(SandboxMessageSummary) bool
	// Interval between polls. Default 1 second.
	Interval time.Duration
}

WaitForOptions tunes WaitFor.

type WebhookDispatch

type WebhookDispatch struct {
	ID          string `json:"id"`
	EndpointID  string `json:"endpointId"`
	EndpointURL string `json:"endpointUrl"`
	// EventID is the id the receiver saw in the body.
	EventID          string           `json:"eventId"`
	Event            WebhookEventType `json:"event"`
	State            string           `json:"state"` // pending, delivering, delivered or dead_lettered
	Attempts         int              `json:"attempts"`
	LastStatusCode   *int             `json:"lastStatusCode"`
	LastError        *string          `json:"lastError"`
	LastAttemptError *string          `json:"lastAttemptError"`
	FirstAttemptAt   *time.Time       `json:"firstAttemptAt"`
	LastAttemptAt    *time.Time       `json:"lastAttemptAt"`
	NextAttemptAt    *time.Time       `json:"nextAttemptAt"`
	CreatedAt        time.Time        `json:"createdAt"`
	ReplayedAt       *time.Time       `json:"replayedAt"`
	ReplayCount      int              `json:"replayCount"`
}

WebhookDispatch is one event's delivery to one endpoint.

type WebhookDispatchPage

type WebhookDispatchPage struct {
	Data []WebhookDispatch `json:"data"`
	Page CursorInfo        `json:"page"`
}

WebhookDispatchPage is one cursor page of dispatches.

type WebhookDispatchesService

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

WebhookDispatchesService reads and replays undelivered events.

func (*WebhookDispatchesService) All

All walks every dispatch matching params, following the cursor.

func (*WebhookDispatchesService) Get

Get returns one dispatch.

func (*WebhookDispatchesService) List

List returns undelivered events: dead letters by default, or State "retrying".

func (*WebhookDispatchesService) Replay

Replay makes one more immediate attempt for a dead-lettered event.

func (*WebhookDispatchesService) ReplayAll

ReplayAll replays every dead letter (optionally of one endpoint), up to the server's cap. params may be nil.

type WebhookEndpoint

type WebhookEndpoint struct {
	ID                      string             `json:"id"`
	URL                     string             `json:"url"`
	Events                  []WebhookEventType `json:"events"`
	Active                  bool               `json:"active"`
	CreatedAt               time.Time          `json:"createdAt"`
	SecretMasked            string             `json:"secretMasked"`
	SecretRotatedAt         *time.Time         `json:"secretRotatedAt"`
	PreviousSecretExpiresAt *time.Time         `json:"previousSecretExpiresAt"`
	SuccessRate             *float64           `json:"successRate"`
	SuccessRateWindowDays   int                `json:"successRateWindowDays"`
	DeliveryCount           int                `json:"deliveryCount"`
	DeliveredCount          int                `json:"deliveredCount"`
}

WebhookEndpoint is a URL SendClipper posts events to.

type WebhookEndpointWithSecret

type WebhookEndpointWithSecret struct {
	WebhookEndpoint
	Secret string `json:"secret"`
}

WebhookEndpointWithSecret carries the full whsec_ signing secret, once.

type WebhookEvent

type WebhookEvent struct {
	// ID is stable for one (email, event): deduplicate on it.
	ID        string           `json:"id"`
	Event     WebhookEventType `json:"event"`
	CreatedAt time.Time        `json:"createdAt"`
	Data      WebhookEventData `json:"data"`
}

WebhookEvent is a webhook delivery's body.

func VerifyWebhook

func VerifyWebhook(payload []byte, headers http.Header, secret string, opts ...VerifyOption) (*WebhookEvent, error)

VerifyWebhook verifies a delivery and returns its parsed event.

payload is the body EXACTLY as received (read r.Body with io.ReadAll before decoding it), never re-encoded JSON; headers is r.Header.

SendClipper signs "{timestamp}.{rawBody}" with HMAC-SHA256 and sends X-SendClipper-Signature-V2: t=<seconds>,v1=<hex>[,v1=<hex>]. During a secret rotation there are two v1= values; any match is accepted.

Example
package main

import (
	"io"
	"log"
	"net/http"
	"os"

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

func main() {
	http.HandleFunc("/hooks/sendclipper", func(w http.ResponseWriter, r *http.Request) {
		payload, err := io.ReadAll(r.Body) // the raw body, before any json.Decode
		if err != nil {
			http.Error(w, "read error", http.StatusBadRequest)
			return
		}
		event, err := sendclipper.VerifyWebhook(payload, r.Header, os.Getenv("SENDCLIPPER_WEBHOOK_SECRET"))
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		// event.ID is stable across retries: deduplicate on it
		log.Println(event.Event, event.Data.To)
		w.WriteHeader(http.StatusOK)
	})
}

func VerifyWebhookSignature

func VerifyWebhookSignature(payload []byte, signature, secret string, opts ...VerifyOption) (*WebhookEvent, error)

VerifyWebhookSignature is VerifyWebhook for when you hold only the X-SendClipper-Signature-V2 header's value.

type WebhookEventData

type WebhookEventData struct {
	EmailID   string `json:"emailId"`
	MessageID string `json:"messageId"`
	To        string `json:"to"`
	Domain    string `json:"domain"`
	Subject   string `json:"subject"`
	// Raw is the whole data object, for event-specific fields (bounce type, clicked link, ...).
	Raw map[string]any `json:"-"`
}

WebhookEventData is the message an event is about. Raw holds every field.

func (*WebhookEventData) UnmarshalJSON

func (d *WebhookEventData) UnmarshalJSON(b []byte) error

UnmarshalJSON keeps every field in Raw as well as the typed ones.

type WebhookEventType

type WebhookEventType string

WebhookEventType names an event a webhook can subscribe to.

const (
	EventDelivered  WebhookEventType = "email.delivered"
	EventOpened     WebhookEventType = "email.opened"
	EventClicked    WebhookEventType = "email.clicked"
	EventBounced    WebhookEventType = "email.bounced"
	EventComplained WebhookEventType = "email.complained"
	EventFailed     WebhookEventType = "email.failed"
)

type WebhookVerificationError

type WebhookVerificationError struct {
	// Reason is missing_header, malformed_header, timestamp_out_of_tolerance,
	// signature_mismatch or invalid_payload.
	Reason  string
	Message string
}

WebhookVerificationError is why VerifyWebhook refused a delivery.

func (*WebhookVerificationError) Error

func (e *WebhookVerificationError) Error() string

type WebhooksService

type WebhooksService struct {
	Dispatches *WebhookDispatchesService
	// contains filtered or unexported fields
}

WebhooksService manages webhook endpoints.

func (*WebhooksService) Create

Create registers an endpoint. The response carries the signing Secret once: store it.

func (*WebhooksService) Deliveries

func (s *WebhooksService) Deliveries(ctx context.Context, params *ListWebhookDeliveriesParams, opts ...RequestOption) ([]map[string]any, error)

Deliveries returns recent delivery attempts.

func (*WebhooksService) List

List returns every endpoint.

func (*WebhooksService) Remove

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

Remove deletes an endpoint.

func (*WebhooksService) RotateSecret

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

RotateSecret makes a new secret. The old one keeps signing alongside it for the grace window.

func (*WebhooksService) Update

Update changes the fields set in params.

func (*WebhooksService) Verify

func (s *WebhooksService) Verify(payload []byte, headers http.Header, secret string, opts ...VerifyOption) (*WebhookEvent, error)

Verify is VerifyWebhook. It makes no request.

type Workspace

type Workspace struct {
	ID            string    `json:"id"`
	Name          string    `json:"name"`
	PublicID      string    `json:"publicId"`
	CreatedAt     time.Time `json:"createdAt"`
	SendingPaused *struct {
		At      time.Time `json:"at"`
		Reason  string    `json:"reason"`
		Message string    `json:"message"`
	} `json:"sendingPaused"`
	PostalAddress *string `json:"postalAddress"`
}

Workspace is the workspace an API key belongs to.

type WorkspaceService

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

WorkspaceService reads the key's workspace.

func (*WorkspaceService) Get

func (s *WorkspaceService) Get(ctx context.Context, opts ...RequestOption) (*Workspace, error)

Get returns the workspace this key belongs to.

Jump to

Keyboard shortcuts

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