duva

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 21 Imported by: 0

README

Duva for Go

The official Duva client library for Go. Duva is a transactional email API hosted in Canada.

go get github.com/duva-mail/duva-go

Requires Go 1.23 or later (iter.Seq2 range-over-func pagination).

Sending a message

package main

import (
	"context"
	"os"

	"github.com/duva-mail/duva-go"
)

func main() {
	client, err := duva.New(os.Getenv("DUVA_API_KEY"), "example.com") // or New("dv_...", "example.com")
	if err != nil {
		panic(err)
	}

	message, err := client.Messages.Send(context.Background(), duva.SendMessageParams{
		From:    "Example <notifications@example.com>",
		To:      []string{"client@example.org"},
		Subject: "Your order",
		Text:    "Thank you for your order.",
	})
	if err != nil {
		panic(err)
	}
	println(message.ID, message.Status) // "queued": always asynchronous
}
To, Cc and Bcc

To, CC and BCC take addresses or Name <address>. Every copy shows all the To and all the CC; a BCC address appears only on its own copy. The three lists together count against your plan's recipient maximum.

message, err := client.Messages.Send(ctx, duva.SendMessageParams{
	From:    "Example <notifications@example.com>",
	To:      []string{"Jean Tremblay <jean@example.org>"},
	CC:      []string{"accounting@example.org"},
	BCC:     []string{"archive@example.com"},
	Subject: "Your order",
	Text:    "Thank you for your order.",
})

Reading events and pagination

for event, err := range client.Events.ListAll(ctx, duva.ListEventsParams{Type: "bounced"}, 0) {
	if err != nil {
		panic(err)
	}
	println(event.Type, event.Detail["recipient"])
}

Events.List / Suppressions.List return one page (.Data, .NextCursor); Events.ListAll / Suppressions.ListAll return an iter.Seq2[*T, error] that follows NextCursor for you, without ever loading every page into memory, bounded by an optional maxItems (0 = unlimited).

Verifying a webhook

event, err := duva.ConstructEvent([]string{os.Getenv("DUVA_WEBHOOK_SECRET")}, r.Header, rawBody, nil)
if err != nil {
	// respond 400
	return
}
println(event.Type, event.Data["message_id"])

rawBody must be the exact bytes Duva sent (read the request body directly; never a re-serialized json.Marshal of a parsed struct): re-encoding it changes the bytes and invalidates the signature. Rotating your webhook secret? Pass both — ConstructEvent([]string{old, new}, ...) — while both are active.

Errors

Every error Duva answers with is one of this package's typed errors; use errors.As for the type you expect, or duva.ErrorCode(err) for the code (the contract — never the message, which can change):

result, err := client.Messages.Send(ctx, params)
var validationErr *duva.ValidationError
var quotaErr *duva.QuotaExceededError
var notFoundErr *duva.NotFoundError
switch {
case errors.As(err, &validationErr):
	fmt.Println(validationErr.Fields) // [{Field: "to[0]", Message: "..."}]
case errors.As(err, &quotaErr):
	fmt.Printf("retry in %s\n", quotaErr.RetryAfter)
case errors.As(err, &notFoundErr):
	// the API key, domain or resource could not be found
}

Network failures and timeouts return *duva.ConnectionError / *duva.TimeoutError instead (no HTTP response was ever received). Reads and Messages.Send (idempotency-key protected) are retried automatically on a transient failure; Suppressions.Add/Remove and Webhooks.Create/Delete are not, because the outcome of a timed-out first attempt is unknown. A 429 quota_exceeded is never retried automatically (its RetryAfter can be hours); a 429 rate_limited is, as long as the wait fits within WithMaxRetryWait (30s by default).

Attachments

attachment, err := duva.NewAttachmentFromFile("./invoice.pdf")
if err != nil {
	panic(err)
}
params.Attachments = []duva.Attachment{attachment}

NewAttachmentFromBytes(filename, content, opts...) works from bytes already in memory; duva.WithContentID(id) turns the attachment into an inline image the HTML references with cid:.

Testing your own application

transport := duva.NewTestTransport(func(req *http.Request) (*http.Response, error) {
	return duva.JSONResponse(200, map[string]any{"status": "ok"}), nil
})
client, _ := duva.New("dv_test", "example.com", duva.WithHTTPClient(transport))
client.Health(ctx)
transport.Requests[0].Path // "/health"

duva.TestTransport implements Doer and records every RecordedRequest it receives: no network call, no separate HTTP mocking library required.

Configuration

New(apiKey, domain string, opts ...Option), with functional options:

Option Default
apiKey / domain args DUVA_API_KEY / DUVA_DOMAIN Required (args win over the environment).
WithBaseURL https://api.duva.ca https only (http://localhost is allowed for tests).
WithTimeout 10s Only used when no WithHTTPClient is given.
WithMaxRetries 2 Network failures / 5xx on a safe-to-retry call.
WithMaxRetryWait 30s A 429 rate_limited with a longer wait is not retried.
WithLanguage unset "en" or "fr": the language of error.message.
WithUserAgent unset Appended to (never replaces) the User-Agent header.
WithHTTPClient &http.Client{Timeout: ...} Inject any Doer (a different *http.Client, a proxy, TestTransport).

Zero third-party dependencies: net/http only.

Full reference

The complete API surface and the OpenAPI specification this library follows: https://duva.ca/en/docs and https://duva.ca/openapi.json.

Development

go build ./...
go vet ./...
bash scripts/generate.sh          # regenerate internal/generated/types.go from the OpenAPI spec
bash scripts/fetch-conformance.sh && go test ./...

Response types are generated from docs/openapi.json (internal/generated/, via oapi-codegen, never edited by hand) and re-exported from the package root (models.go) so callers only ever import duva. The client itself (retries, pagination, errors, webhooks) is hand-written and checked against the shared fixtures published in duva-mail/duva-conformance.

License

MIT, see LICENSE.

Documentation

Overview

Small formatting helpers matched to the API's own parsing rules (docs/api.md in the duva repository).

Attachment helpers. The server remains the authority on the limits below (they can change): these are a courtesy, so a mistake fails locally instead of after an upload.

Package duva is the official Duva client library. Duva is a transactional email API hosted in Canada. See https://duva.ca and https://duva.ca/en/docs for the full HTTP reference this package wraps.

client, err := duva.New("dv_...", "example.com") // or DUVA_API_KEY / DUVA_DOMAIN
message, err := client.Messages.Send(ctx, duva.SendMessageParams{
	From:    "Example <notifications@example.com>",
	To:      []string{"client@example.org"},
	Subject: "Your order",
	Text:    "Thank you for your order.",
})

TestTransport: a fake Doer and a response builder, for testing YOUR OWN application without a real network call. Pass it via WithHTTPClient when constructing a Client in your own tests.

Transport: builds requests, applies the retry policy of docs/bibliotheques-clientes.md section 3.5 (in the duva repository) exactly, and turns a Duva error response into the right typed error.

Webhook signature verification, "Standard Webhooks" format (see docs/api.md "Webhooks" in the duva repository). Duva SENDS webhooks; this file is for VERIFYING them on your side.

Index

Constants

View Source
const (
	MaxAttachments          = 10
	MaxAttachmentTotalBytes = 5 * 1024 * 1024
)

Mirrors services/messages.py in the duva repository at the time of writing: 10 attachments, 5 MB decoded in total, these extensions refused. Re-check against docs/api.md if this drifts.

Variables

This section is empty.

Functions

func ErrorCode

func ErrorCode(err error) (string, bool)

ErrorCode returns err's error.code when err is one of this package's typed API errors (see APIError), and whether it was found. Useful to branch on the code without an errors.As for every possible type.

func FormatAddress

func FormatAddress(address, name string) string

FormatAddress returns "Name <address>" (Name quoted if it contains a `"` or `,`), or just address when name is empty.

func JSONResponse

func JSONResponse(status int, body any) *http.Response

JSONResponse builds an *http.Response for a TestTransport handler: status, with body encoded as JSON.

func SignWebhookRequest

func SignWebhookRequest(secret, eventID string, body []byte, timestamp time.Time) http.Header

SignWebhookRequest builds a validly signed request FOR YOUR OWN TESTS: the headers a real Duva webhook delivery would carry for body, signed with secret as of timestamp (the zero value means now). Never used by this package to send anything: Duva is the only real sender.

func UnsubscribeHeaders

func UnsubscribeHeaders(opts UnsubscribeOptions) (http.Header, error)

UnsubscribeHeaders builds List-Unsubscribe (and List-Unsubscribe-Post for one-click) exactly as the API validates them: at most 3 links, https:// or mailto: only.

func VerifyWebhookSignature

func VerifyWebhookSignature(secrets []string, headers http.Header, rawBody []byte, opts *VerifyOptions) bool

VerifyWebhookSignature verifies a webhook request. secrets accepts one or several (for key rotation: while both the old and the new secret are active, a webhook signed with either must verify).

headers is looked up case-insensitively (http.Header always is). rawBody must be the EXACT bytes Duva sent: re-encoding a parsed-then-re-serialized JSON body changes its bytes and invalidates every signature (see your framework's raw-body option, documented per-framework in docs/api.md).

Types

type APIError

type APIError struct {
	StatusCode int
	Code       string
	Message    string
	Fields     []ValidationField
	RawBody    string // bounded to 4 KB; never your API key
}

APIError is the common shape of every error Duva actually answered (as opposed to a network failure: see *ConnectionError and *TimeoutError). Every typed error below embeds one: use errors.As with the specific type (e.g. *NotFoundError) to match it, or compare .Code directly for a code this package doesn't have a dedicated type for yet.

func (*APIError) Error

func (e *APIError) Error() string

type Attachment

type Attachment = generated.Attachment

Attachment is both the wire format Messages.Send sends and what NewAttachmentFromBytes / NewAttachmentFromFile build; see attachment.go.

func NewAttachmentFromBytes

func NewAttachmentFromBytes(filename string, content []byte, opts ...AttachmentOption) (Attachment, error)

NewAttachmentFromBytes builds an Attachment from bytes already in memory.

func NewAttachmentFromFile

func NewAttachmentFromFile(path string, opts ...AttachmentOption) (Attachment, error)

NewAttachmentFromFile reads a file from disk.

type AttachmentOption

type AttachmentOption func(*Attachment)

AttachmentOption sets an optional field on an Attachment built by NewAttachmentFromBytes or NewAttachmentFromFile.

func WithContentID

func WithContentID(contentID string) AttachmentOption

WithContentID turns the attachment into an inline image the HTML body references with cid:.

func WithContentType

func WithContentType(contentType string) AttachmentOption

WithContentType sets the attachment's MIME type (guessed by the server when omitted).

type AuthenticationError

type AuthenticationError struct{ *APIError }

AuthenticationError: 401 unauthorized.

type Client

type Client struct {
	Messages     *MessagesService
	Events       *EventsService
	Suppressions *SuppressionsService
	Webhooks     *WebhooksService
	Stats        *StatsService
	// contains filtered or unexported fields
}

Client is bound to one domain and its API key (an API key only ever opens one domain).

client, err := duva.New("dv_...", "example.com") // or DUVA_API_KEY / DUVA_DOMAIN

func New

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

New builds a Client. apiKey and domain fall back to DUVA_API_KEY / DUVA_DOMAIN when empty.

func (*Client) Health

func (c *Client) Health(ctx context.Context) (*HealthStatus, error)

Health checks Duva's own health (GET /health). A 503 or unreachable API returns an error.

type ConflictError

type ConflictError struct{ *APIError }

ConflictError: 409 idempotency_conflict or limit_reached.

type ConnectionError

type ConnectionError struct{ Err error }

ConnectionError: no response was received at all (DNS, TLS, connection refused, reset...).

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type CreateWebhookParams

type CreateWebhookParams struct {
	URL    string
	Events []string
}

CreateWebhookParams: URL is required.

type Doer

type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

Doer is the minimal HTTP client interface this package needs: *http.Client satisfies it, and so does any test double or wrapper (proxying, logging, mTLS). See TestTransport for testing your own application without a real network call.

type Event

type Event = generated.Event

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type EventPage

type EventPage = generated.EventPage

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type EventType

type EventType = generated.EventType

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type EventsService

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

EventsService is Client.Events.

func (*EventsService) List

func (s *EventsService) List(ctx context.Context, params ListEventsParams) (*EventPage, error)

List reads one page.

func (*EventsService) ListAll

func (s *EventsService) ListAll(ctx context.Context, params ListEventsParams, maxItems int) iter.Seq2[*Event, error]

ListAll follows next_cursor across every page, never loading them all into memory at once. maxItems <= 0 means unlimited. The cursor stays opaque: never built or interpreted here.

for event, err := range client.Events.ListAll(ctx, duva.ListEventsParams{}, 0) {
	if err != nil { ... }
}

type GetStatsParams

type GetStatsParams struct {
	Granularity string
	Since       *time.Time
	Until       *time.Time
}

GetStatsParams: Granularity is "day" (default) or "hour"; periods are in UTC.

type HealthStatus

type HealthStatus struct {
	Status string `json:"status"`
}

HealthStatus is what Health returns.

type ListEventsParams

type ListEventsParams struct {
	MessageID string
	Type      string
	Recipient string
	Since     *time.Time
	Limit     int
	Cursor    string
}

ListEventsParams filters one page of List / ListAll.

type ListSuppressionsParams

type ListSuppressionsParams struct {
	Reason string
	Limit  int
	Cursor string
}

ListSuppressionsParams filters one page of List / ListAll.

type Message

type Message = generated.Message

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type MessageAccepted

type MessageAccepted = generated.MessageAccepted

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type MessageAcceptedStatus

type MessageAcceptedStatus = generated.MessageAcceptedStatus

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type MessageStatus

type MessageStatus = generated.MessageStatus

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type MessagesService

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

MessagesService is Client.Messages.

func (*MessagesService) Get

func (s *MessagesService) Get(ctx context.Context, id string) (*Message, error)

Get reads a message, with its recipients.

func (*MessagesService) Send

Send sends a message: always asynchronous ("queued"), never returns a delivery outcome.

type NotFoundError

type NotFoundError struct{ *APIError }

NotFoundError: 404 not_found.

type Option

type Option func(*config)

Option configures a Client created by New.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the default https://api.duva.ca (http://localhost is allowed, for tests).

func WithHTTPClient

func WithHTTPClient(client Doer) Option

WithHTTPClient injects your own Doer (proxy, mTLS, logging, or TestTransport for tests) instead of the default *http.Client.

func WithLanguage

func WithLanguage(lang string) Option

WithLanguage sets Accept-Language ("en" or "fr"): the language of error.message.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a safe-to-retry call is retried after a transient failure (2 by default; 0 disables retries).

func WithMaxRetryWait

func WithMaxRetryWait(d time.Duration) Option

WithMaxRetryWait sets the longest Retry-After a 429 rate_limited is retried for (30s by default); a longer wait raises *RateLimitError instead.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-attempt request timeout (10s by default). Only takes effect when no WithHTTPClient is given: your own *http.Client keeps its own timeout.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent appends text to the User-Agent header (never replaces it), for diagnostics.

type PayloadTooLargeError

type PayloadTooLargeError struct{ *APIError }

PayloadTooLargeError: 413 payload_too_large.

type PermissionError

type PermissionError struct{ *APIError }

PermissionError: 403 domain_not_verified or sending_not_allowed (.Code distinguishes the two).

type QuotaExceededError

type QuotaExceededError struct {
	*APIError
	RetryAfter time.Duration
}

QuotaExceededError: a 429 on YOUR ACCOUNT quota (daily or monthly). RetryAfter can be hours: never retried automatically, by design (see docs/bibliotheques-clientes.md section 3.5 in the duva repository).

type RateLimitError

type RateLimitError struct {
	*APIError
	RetryAfter time.Duration
}

RateLimitError: a 429 from the per-key rate limit (unrelated to your sending quota). Retried automatically when RetryAfter fits within the client's WithMaxRetryWait.

type Recipient

type Recipient = generated.Recipient

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type RecipientStatus

type RecipientStatus = generated.RecipientStatus

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type RecordedRequest

type RecordedRequest struct {
	Method string
	Path   string
	Query  url.Values
	Header http.Header
	Body   []byte
}

RecordedRequest is one request TestTransport received, kept for assertions in your tests.

type SendMessageParams

type SendMessageParams struct {
	From string
	To   []string
	// To, CC and BCC take addresses or "Name <address>"; together they count against the plan's
	// recipient maximum. Every copy shows all the To and all the CC addresses; a BCC address
	// appears only on its own copy.
	CC          []string
	BCC         []string
	Subject     string
	HTML        string
	Text        string
	Tags        []string
	Tracking    *Tracking
	ReplyTo     string
	Headers     map[string]string
	Metadata    map[string]string
	Attachments []Attachment
	// IdempotencyKey: generated (UUID v4) when empty. 1 to 255 printable ASCII characters, no
	// spaces, unique per domain, when you supply your own. A key generated per call does not
	// de-duplicate BETWEEN two calls: to de-duplicate a logical send (an order), pass a stable key
	// ("order-4821").
	IdempotencyKey string
}

SendMessageParams: From, To and Subject are required; at least one of HTML and Text too. Nothing more is checked locally: recopying the server's own rules would drift, and it answers 422 with Fields regardless.

type SendMessageResult

type SendMessageResult struct {
	ID     string
	Status string
	// Replayed is true when this answer replays an earlier identical request (the
	// Idempotent-Replayed response header): a retried call with the same idempotency key never
	// creates a duplicate.
	Replayed bool
	// Location is GET /v1/{domain}/messages/{id}'s address (the Location response header), or
	// empty if absent.
	Location string
}

SendMessageResult is what Messages.Send returns: the accepted message id and status, plus what the response headers say about idempotency replay and the new message's location.

type ServerError

type ServerError struct{ *APIError }

ServerError: a 5xx, or a response whose body was not the documented error envelope.

type Stats

type Stats = generated.Stats

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type StatsGranularity

type StatsGranularity = generated.StatsGranularity

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type StatsPeriod

type StatsPeriod = generated.StatsPeriod

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type StatsService

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

StatsService is Client.Stats.

func (*StatsService) Get

func (s *StatsService) Get(ctx context.Context, params GetStatsParams) (*Stats, error)

Get reads aggregated send statistics for this domain.

type Suppression

type Suppression = generated.Suppression

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type SuppressionPage

type SuppressionPage = generated.SuppressionPage

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type SuppressionReason

type SuppressionReason = generated.SuppressionReason

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type SuppressionsService

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

SuppressionsService is Client.Suppressions.

func (*SuppressionsService) Add

func (s *SuppressionsService) Add(ctx context.Context, email string) (suppression *Suppression, created bool, err error)

Add suppresses an address. created is true on a 201 (newly added), false on a 200 (already suppressed, for this reason or another).

func (*SuppressionsService) List

List reads one page.

func (*SuppressionsService) ListAll

ListAll follows next_cursor across every page, never loading them all into memory at once. maxItems <= 0 means unlimited.

func (*SuppressionsService) Remove

func (s *SuppressionsService) Remove(ctx context.Context, email string) error

Remove un-suppresses an address. A *NotFoundError means it wasn't suppressed.

type TestTransport

type TestTransport struct {
	Requests []RecordedRequest
	// contains filtered or unexported fields
}

TestTransport is a fake Doer: no network call. handler decides the response; every request is recorded in Requests, in order.

func NewTestTransport

func NewTestTransport(handler func(*http.Request) (*http.Response, error)) *TestTransport

NewTestTransport builds a TestTransport that answers every request with handler.

func (*TestTransport) Do

func (t *TestTransport) Do(req *http.Request) (*http.Response, error)

Do implements Doer.

type TimeoutError

type TimeoutError struct{ Err error }

TimeoutError: the request exceeded the configured timeout before any response arrived.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

func (*TimeoutError) Unwrap

func (e *TimeoutError) Unwrap() error

type Tracking

type Tracking = generated.Tracking

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type UnsubscribeOptions

type UnsubscribeOptions struct {
	HTTPSURL string
	Mailto   string
	// OneClick adds List-Unsubscribe-Post (RFC 8058). Defaults to true when HTTPSURL is set,
	// false otherwise.
	OneClick *bool
}

UnsubscribeOptions configures UnsubscribeHeaders.

type ValidationError

type ValidationError struct{ *APIError }

ValidationError: 422 invalid_request. .Fields lists the offending fields.

type ValidationField

type ValidationField struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

ValidationField is one entry of a *ValidationError's Fields (e.g. {Field: "to[0]", Message: "..."}).

type VerifyOptions

type VerifyOptions struct {
	// ToleranceSeconds: 0 means the default (300s).
	ToleranceSeconds int
	// Now: the current instant, for YOUR OWN tests only (see SignWebhookRequest and the fixtures
	// of duva-mail/duva-conformance, which document the exact instant each vector was signed at).
	// The zero value means time.Now().
	Now time.Time
}

VerifyOptions configures VerifyWebhookSignature and ConstructEvent. The zero value uses the defaults (5 minute tolerance, the real current time).

type Webhook

type Webhook = generated.Webhook

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhookDelivery

type WebhookDelivery = generated.WebhookDelivery

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhookDeliveryList

type WebhookDeliveryList = generated.WebhookDeliveryList

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhookDeliveryStatus

type WebhookDeliveryStatus = generated.WebhookDeliveryStatus

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhookEvent

type WebhookEvent struct {
	ID     string         `json:"id"`
	Type   string         `json:"type"`
	Domain string         `json:"domain"`
	Data   map[string]any `json:"data"`
}

WebhookEvent is the JSON body Duva sends to your webhook URL, already parsed.

func ConstructEvent

func ConstructEvent(secrets []string, headers http.Header, rawBody []byte, opts *VerifyOptions) (*WebhookEvent, error)

ConstructEvent: VerifyWebhookSignature, then parses the body. Returns a *WebhookSignatureError on a bad signature rather than a boolean, for call sites that want to fail closed. Never includes the secret or the raw body in the error.

type WebhookList

type WebhookList = generated.WebhookList

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhookSignatureError

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

WebhookSignatureError: a webhook signature failed to verify. Never carries the secret or body.

func (*WebhookSignatureError) Error

func (e *WebhookSignatureError) Error() string

type WebhookStatus

type WebhookStatus = generated.WebhookStatus

Response types, generated from docs/openapi.json (internal/generated, never edited by hand; see scripts/generate.sh). Re-exported here so callers only ever import this package.

type WebhooksService

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

WebhooksService is Client.Webhooks.

func (*WebhooksService) Create

func (s *WebhooksService) Create(ctx context.Context, params CreateWebhookParams) (*Webhook, error)

Create registers a webhook endpoint. The returned Webhook's Secret is present ONLY in this response: it never comes back from List or Get. Never logged, never retried (the outcome of a timed-out first attempt is unknown).

func (*WebhooksService) Delete

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

Delete removes a webhook endpoint. Not safe to retry.

func (*WebhooksService) Deliveries

func (s *WebhooksService) Deliveries(ctx context.Context, id string, limit int) ([]WebhookDelivery, error)

Deliveries lists this webhook's delivery attempts (most recent first). limit <= 0 uses the server's default page size.

func (*WebhooksService) Get

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

Get reads one webhook endpoint.

func (*WebhooksService) List

func (s *WebhooksService) List(ctx context.Context) ([]Webhook, error)

List returns every webhook endpoint for this domain.

Directories

Path Synopsis
internal
generated
Package generated provides primitives to interact with the openapi HTTP API.
Package generated provides primitives to interact with the openapi HTTP API.

Jump to

Keyboard shortcuts

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