Documentation
¶
Overview ¶
Package mailtea is the official Go SDK for Mailtea — a thin, typed wrapper over the REST API at https://docs.mailtea.app/docs/api-reference.
It has no dependencies outside the standard library.
client, err := mailtea.New(os.Getenv("MAILTEA_API_KEY"))
if err != nil {
log.Fatal(err)
}
sent, err := client.Emails.Send(ctx, mailtea.SendEmailRequest{
From: "you@yourdomain.com",
To: []string{"recipient@example.com"},
Subject: "Hello from Mailtea",
HTML: "<p>Your first email.</p>",
})
Every method takes a context.Context and returns (result, error). Errors from the API are *mailtea.Error, reachable with errors.As.
Index ¶
- Constants
- func SignWebhook(secret, msgID string, timestamp int64, payload string) string
- func VerifyWebhookSignature(secret, msgID, timestamp, payload, signatureHeader string) bool
- func VerifyWebhookSignatureAt(secret, msgID, timestamp, payload, signatureHeader string, ...) bool
- type APIKeysService
- type AssetsService
- type Attachment
- type AttachmentMeta
- type AutomationRunsService
- func (s *AutomationRunsService) Cancel(ctx context.Context, automationID, runID string, params Params) (Object, error)
- func (s *AutomationRunsService) Get(ctx context.Context, automationID, runID string, params Params) (Object, error)
- func (s *AutomationRunsService) List(ctx context.Context, automationID string, params Params) (*List, error)
- type AutomationsService
- func (s *AutomationsService) Activate(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Archive(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Create(ctx context.Context, params Params) (Object, error)
- func (s *AutomationsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) List(ctx context.Context, params Params) (*List, error)
- func (s *AutomationsService) Metrics(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Pause(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Test(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Update(ctx context.Context, id string, params Params) (Object, error)
- func (s *AutomationsService) Validate(ctx context.Context, params Params) (Object, error)
- func (s *AutomationsService) Version(ctx context.Context, id string, version interface{}, params Params) (Object, error)
- func (s *AutomationsService) Versions(ctx context.Context, id string, params Params) (Object, error)
- type BatchResponse
- type Client
- type ContactPropertiesService
- func (s *ContactPropertiesService) Create(ctx context.Context, params Params) (Object, error)
- func (s *ContactPropertiesService) Delete(ctx context.Context, id string) (Object, error)
- func (s *ContactPropertiesService) List(ctx context.Context, params Params) (*List, error)
- func (s *ContactPropertiesService) Update(ctx context.Context, id string, params Params) (Object, error)
- type ContactsService
- func (s *ContactsService) Create(ctx context.Context, request CreateContactRequest) (Object, error)
- func (s *ContactsService) Delete(ctx context.Context, idOrEmail string, params Params) (Object, error)
- func (s *ContactsService) Get(ctx context.Context, idOrEmail string, params Params) (Object, error)
- func (s *ContactsService) List(ctx context.Context, params Params) (*List, error)
- func (s *ContactsService) Update(ctx context.Context, idOrEmail string, request UpdateContactRequest) (Object, error)
- func (s *ContactsService) Upsert(ctx context.Context, request CreateContactRequest) (Object, error)
- type CreateContactRequest
- type CreatePostRequest
- type CreateTopicRequest
- type DomainsService
- func (s *DomainsService) Create(ctx context.Context, params Params) (Object, error)
- func (s *DomainsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *DomainsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *DomainsService) List(ctx context.Context, params Params) (*List, error)
- func (s *DomainsService) Update(ctx context.Context, id string, params Params) (Object, error)
- func (s *DomainsService) Verify(ctx context.Context, id string, params Params) (Object, error)
- type DroppedRecipient
- type Email
- type EmailsService
- func (s *EmailsService) Analytics(ctx context.Context, params Params) (Object, error)
- func (s *EmailsService) Batch(ctx context.Context, requests []SendEmailRequest) (*BatchResponse, error)
- func (s *EmailsService) Cancel(ctx context.Context, id string) (Object, error)
- func (s *EmailsService) Get(ctx context.Context, id string) (*Email, error)
- func (s *EmailsService) List(ctx context.Context, params Params) (*List, error)
- func (s *EmailsService) Reschedule(ctx context.Context, id, scheduledAt string) (Object, error)
- func (s *EmailsService) Send(ctx context.Context, request SendEmailRequest) (*SendEmailResponse, error)
- func (s *EmailsService) Update(ctx context.Context, id string, request UpdateEmailRequest) (Object, error)
- type Error
- type EventDefinitionsService
- func (s *EventDefinitionsService) Create(ctx context.Context, params Params) (Object, error)
- func (s *EventDefinitionsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *EventDefinitionsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *EventDefinitionsService) List(ctx context.Context, params Params) (*List, error)
- func (s *EventDefinitionsService) Update(ctx context.Context, id string, params Params) (Object, error)
- type EventsService
- type HTTPDoer
- type InboundAttachmentsService
- type InboundService
- type List
- type Object
- type Option
- type Params
- type PostsService
- func (s *PostsService) Create(ctx context.Context, request CreatePostRequest) (Object, error)
- func (s *PostsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *PostsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *PostsService) List(ctx context.Context, params Params) (*List, error)
- func (s *PostsService) Send(ctx context.Context, id string, request SendPostRequest) (Object, error)
- func (s *PostsService) SendTest(ctx context.Context, id string, request SendTestPostRequest) (*SendTestPostResponse, error)
- func (s *PostsService) Update(ctx context.Context, id string, params Params) (Object, error)
- type SegmentsService
- func (s *SegmentsService) Create(ctx context.Context, params Params) (Object, error)
- func (s *SegmentsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *SegmentsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *SegmentsService) List(ctx context.Context, params Params) (*List, error)
- func (s *SegmentsService) Update(ctx context.Context, id string, params Params) (Object, error)
- type SendEmailRequest
- type SendEmailResponse
- type SendPostRequest
- type SendTestPostRequest
- type SendTestPostResponse
- type SendersService
- func (s *SendersService) Create(ctx context.Context, params Params) (Object, error)
- func (s *SendersService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *SendersService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *SendersService) List(ctx context.Context, params Params) (*List, error)
- func (s *SendersService) Update(ctx context.Context, id string, params Params) (Object, error)
- type SuppressionsService
- func (s *SuppressionsService) Add(ctx context.Context, params Params) (Object, error)
- func (s *SuppressionsService) Export(ctx context.Context) (string, error)
- func (s *SuppressionsService) List(ctx context.Context, params Params) (*List, error)
- func (s *SuppressionsService) Remove(ctx context.Context, params Params) (Object, error)
- type Tag
- type TemplateRef
- type TemplatesService
- func (s *TemplatesService) Create(ctx context.Context, params Params) (Object, error)
- func (s *TemplatesService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) Duplicate(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) List(ctx context.Context, params Params) (*List, error)
- func (s *TemplatesService) Publish(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) Render(ctx context.Context, params Params) (Object, error)
- func (s *TemplatesService) RestoreVersion(ctx context.Context, id string, version interface{}, params Params) (Object, error)
- func (s *TemplatesService) Unpublish(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) Update(ctx context.Context, id string, params Params) (Object, error)
- func (s *TemplatesService) Versions(ctx context.Context, id string, params Params) (Object, error)
- type TopicsService
- func (s *TopicsService) Create(ctx context.Context, request CreateTopicRequest) (Object, error)
- func (s *TopicsService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *TopicsService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *TopicsService) List(ctx context.Context, params Params) (*List, error)
- func (s *TopicsService) Update(ctx context.Context, id string, params Params) (Object, error)
- type TrackingDomainsService
- func (s *TrackingDomainsService) Create(ctx context.Context, domainID string, params Params) (Object, error)
- func (s *TrackingDomainsService) Delete(ctx context.Context, domainID, trackingDomainID string, params Params) (Object, error)
- func (s *TrackingDomainsService) List(ctx context.Context, domainID string, params Params) (Object, error)
- func (s *TrackingDomainsService) Verify(ctx context.Context, domainID, trackingDomainID string, params Params) (Object, error)
- type UpdateContactRequest
- type UpdateEmailRequest
- type WebhooksService
- func (s *WebhooksService) Create(ctx context.Context, params Params) (Object, error)
- func (s *WebhooksService) Delete(ctx context.Context, id string, params Params) (Object, error)
- func (s *WebhooksService) Get(ctx context.Context, id string, params Params) (Object, error)
- func (s *WebhooksService) List(ctx context.Context, params Params) (*List, error)
- func (s *WebhooksService) Update(ctx context.Context, id string, params Params) (Object, error)
Constants ¶
const DefaultBaseURL = "https://api.mailtea.app"
DefaultBaseURL is the hosted Mailtea API.
const ( // DefaultWebhookTolerance is how far the delivery's timestamp may sit from // now, each way, before it is treated as a replay. Five minutes, the // Standard Webhooks default. DefaultWebhookTolerance = 5 * time.Minute )
const Version = "0.1.0"
Version is this SDK's release. It is sent on every request as `User-Agent: mailtea-go/<Version>`, which is how a support request can be traced back to the client that made it.
The mirror repo's `v<Version>` git tag IS the Go release — pkg.go.dev indexes the tag, there is no separate registry upload — so this constant and that tag must always agree.
Variables ¶
This section is empty.
Functions ¶
func SignWebhook ¶
SignWebhook signs a payload and returns the `webhook-signature` header value in Standard Webhooks form, `v1,<base64 HMAC-SHA256>`. Useful for faking Mailtea deliveries in tests.
timestamp is Unix seconds — the same value sent in `webhook-timestamp`.
func VerifyWebhookSignature ¶
VerifyWebhookSignature checks a `webhook-signature` header against the expected HMAC.
ok := mailtea.VerifyWebhookSignature(
signingSecret, // whsec_… from Webhooks.Create
r.Header.Get("webhook-id"),
r.Header.Get("webhook-timestamp"),
string(rawBody), // exact bytes received, not re-serialized
r.Header.Get("webhook-signature"),
)
The header may carry several space-delimited `v1,<sig>` tokens — Standard Webhooks allows key rotation, and the platform may sign one delivery with both the old and the new secret — so a match against any `v1` token passes.
It returns false when the timestamp is outside DefaultWebhookTolerance of now, which is the replay protection. The comparison is constant-time, and a bad signature is a false rather than an error.
func VerifyWebhookSignatureAt ¶
func VerifyWebhookSignatureAt( secret, msgID, timestamp, payload, signatureHeader string, tolerance time.Duration, now time.Time, ) bool
VerifyWebhookSignatureAt is VerifyWebhookSignature with the tolerance and the current time supplied — the injectable form, so a test can prove that an expired timestamp is rejected without sleeping for five minutes.
Types ¶
type APIKeysService ¶
type APIKeysService struct {
// contains filtered or unexported fields
}
APIKeysService is the `api_keys` resource. Reach it as client.APIKeys.
Requires a token with `settings:write`. A key can never be granted scopes the calling token does not already hold.
func (*APIKeysService) Create ¶
Create mints an API key. The `token` is returned ONCE — store it securely.
Takes name, optional permission ("full_access" or "sending_access"), and optional domain_id.
type AssetsService ¶
type AssetsService struct {
// contains filtered or unexported fields
}
AssetsService is the `assets` resource — a publication's image library. Reach it as client.Assets.
An email or site image block needs an absolute URL, so this is how a picture that is not already in the library gets into one. Pointing an image at a host you do not control breaks the day that host moves the file.
PNG, JPEG, GIF, WebP or SVG, 5 MB per image. The bytes are checked against the declared content_type, so a mislabelled file is rejected rather than stored.
func (*AssetsService) Delete ¶
Delete retires an asset.
The stored file is KEPT and its URL keeps resolving, so images inside already-sent emails do not break. This hides the asset from the library — it does not remove it from any email, template or page referencing it.
func (*AssetsService) List ¶
List lists the library, newest first. Filters: publication_id (required), search (file name), limit (1-200, default 100).
func (*AssetsService) Upload ¶
Upload puts an image in the library and returns it, including the `url` to use as an image block's src.
Takes publication_id, content, content_type and filename. `content` may be raw []byte — base64-encoded for you — or a string that is already base64:
raw, _ := os.ReadFile("hero.png")
asset, err := client.Assets.Upload(ctx, mailtea.Params{
"publication_id": "pub_123",
"content": raw,
"content_type": "image/png",
"filename": "hero.png",
})
asset.String("url")
type Attachment ¶
type Attachment struct {
Filename string `json:"filename"`
Content string `json:"content"`
ContentType string `json:"content_type,omitempty"`
ContentID string `json:"content_id,omitempty"`
}
Attachment is a file sent with an email. Content is base64. Set ContentType and a ContentID to embed an inline image referenced by `cid:` in the HTML; omit ContentID for an ordinary file attachment.
type AttachmentMeta ¶
type AttachmentMeta struct {
Filename string `json:"filename"`
ContentType string `json:"content_type"`
Size int64 `json:"size"`
}
AttachmentMeta describes an attachment on a retrieved email. The bytes are not returned.
type AutomationRunsService ¶
type AutomationRunsService struct {
// contains filtered or unexported fields
}
AutomationRunsService is the `automation_runs` resource — one contact's journey through one automation. Reach it as client.AutomationRuns.
Runs are nested under an automation and scoped to a publication. A run PINS the automation version it started on, so Get returns the graph the run is actually executing, not the live one.
func (*AutomationRunsService) Cancel ¶
func (s *AutomationRunsService) Cancel(ctx context.Context, automationID, runID string, params Params) (Object, error)
Cancel stops one in-flight run. Requires publication_id. A cancelled run cannot be resumed. Returns the run in full detail.
func (*AutomationRunsService) Get ¶
func (s *AutomationRunsService) Get(ctx context.Context, automationID, runID string, params Params) (Object, error)
Get retrieves one run in full. Requires publication_id. Returns the PINNED steps/connections, the per-step step_runs, and `waiting` (resume_at / waiting_event_name) — read this rather than an event ingest's resumed_runs counter to tell whether an event actually advanced the run.
func (*AutomationRunsService) List ¶
func (s *AutomationRunsService) List(ctx context.Context, automationID string, params Params) (*List, error)
List lists an automation's runs, cursor-paginated. Filters: publication_id (required), status (one status or a []string of them, joined for you), contact_id, is_test, limit, after. List items omit the pinned graph and the step runs; use Get for those.
A Go bool renders as "true"/"false" here, which is what the server matches `is_test` against — it 400s on anything else.
type AutomationsService ¶
type AutomationsService struct {
// contains filtered or unexported fields
}
AutomationsService is the `automations` resource — multi-step contact journeys. Reach it as client.Automations.
Automations are scoped to a publication. An automation is a graph: `steps` (each {"key", "type", "label", "config"}) plus optional `connections` (each {"from", "to", "branch"}).
`connections` is optional: omit it and the server links the steps in array order with branch "next", rooted at the trigger. A graph containing a `condition` or `wait_for_event` step cannot be inferred that way and is rejected with `connections_required_for_branching` — send its connections explicitly.
Failures come back as coded `issues[]` rather than schema errors, and for a draft/paused/archived automation they ride along informationally instead of blocking the save.
func (*AutomationsService) Activate ¶
func (s *AutomationsService) Activate(ctx context.Context, id string, params Params) (Object, error)
Activate starts the automation so new contacts enroll. Requires publication_id. A graph with errors is refused with 422 `automation_invalid` and the blocking issues[].
func (*AutomationsService) Archive ¶
Archive archives the automation. Requires publication_id (query). Optional cancel_runs — it DEFAULTS TO TRUE here, the opposite of Pause, so in-flight runs exit with `automation_archived`. Returns the automation plus canceled_runs.
func (*AutomationsService) Create ¶
Create adds an automation. Takes publication_id, name and steps, plus optional description, connections, reentry_policy (once/once_per_window/always — once_per_window requires reentry_window_seconds), on_step_failure and validate_only. With validate_only nothing is written and an automation_validation comes back instead. New automations start as draft — Activate starts them.
func (*AutomationsService) Delete ¶
Delete removes an automation. Requires publication_id. Deleting an `active` automation is a 409 `automation_active` — pause or archive it first so its in-flight runs are not dropped silently.
func (*AutomationsService) Get ¶
Get retrieves one automation with its live graph and current issues[]. Requires publication_id.
func (*AutomationsService) List ¶
List lists automations, cursor-paginated. Filters: publication_id (required), status (draft/active/paused/archived), limit, after. List items omit steps, connections, valid and issues — use Get for the full graph.
func (*AutomationsService) Metrics ¶
Metrics returns per-step funnel counts. Filters: publication_id (required), version (omit to aggregate across ALL versions), since, until (ISO 8601). Test runs are always excluded (excludes_test_runs: true). Condition steps report branches {condition_met, condition_not_met}; wait_for_event steps {event_received, timeout}.
func (*AutomationsService) Pause ¶
Pause stops new enrollments. Requires publication_id (query). Optional cancel_runs — it DEFAULTS TO FALSE here, so in-flight runs keep going; pass cancel_runs true to exit them. Returns the automation plus canceled_runs.
func (*AutomationsService) Test ¶
Test runs the automation once against a real contact. publication_id is required and is sent as a query parameter; the body takes one of contact_id or email, plus optional event_properties to seed the run's `event.*` namespace.
A test run SENDS REAL, BILLED EMAIL to that inbox — it does not bypass any send gate. It is flagged is_test and excluded from Metrics. Returns 202 with the queued run.
func (*AutomationsService) Update ¶
Update changes an automation's name, description, steps, connections, reentry_policy, reentry_window_seconds or on_step_failure. publication_id is required and is sent as a query parameter ONLY — this endpoint rejects it in the body. The graph is replaced wholesale and cuts a new version.
validate_only returns an automation_validation and writes nothing. A graph change carrying errors saves anyway while the automation is draft/paused/archived; on an `active` one it is a 422 — pause, save, then start again.
func (*AutomationsService) Validate ¶
Validate dry-runs a graph without creating anything. Takes publication_id and steps, plus optional connections. Returns {"object": "automation_validation", "valid": ..., "issues": [...]}.
func (*AutomationsService) Version ¶
func (s *AutomationsService) Version(ctx context.Context, id string, version interface{}, params Params) (Object, error)
Version retrieves one stored version, including its steps and connections. Requires publication_id. This is the graph a run of that version is pinned to — editing the automation never rewrites it.
func (*AutomationsService) Versions ¶
func (s *AutomationsService) Versions(ctx context.Context, id string, params Params) (Object, error)
Versions lists an automation's versions, cursor-paginated. Filters: publication_id (required), limit, after. List items carry no steps/connections — use Version for a stored graph.
type BatchResponse ¶
type BatchResponse struct {
Data []SendEmailResponse `json:"data"`
}
BatchResponse is what a batch send returns — one id per message, in order.
type Client ¶
type Client struct {
Emails *EmailsService
Contacts *ContactsService
Segments *SegmentsService
Topics *TopicsService
Posts *PostsService
Senders *SendersService
Assets *AssetsService
Suppressions *SuppressionsService
Templates *TemplatesService
Domains *DomainsService
Webhooks *WebhooksService
ContactProperties *ContactPropertiesService
APIKeys *APIKeysService
Automations *AutomationsService
AutomationRuns *AutomationRunsService
Events *EventsService
EventDefinitions *EventDefinitionsService
// contains filtered or unexported fields
}
Client talks to one Mailtea instance with one API key. It is safe for concurrent use by multiple goroutines.
func New ¶
New builds a client.
The API key is an mt_pat_… or mt_svc_… token. Pass it explicitly, or pass "" to read MAILTEA_API_KEY from the environment. With neither, New returns a *Error with Status 0 and Code "missing_api_key" — the misconfiguration is reported where it happened rather than as a 401 on the first send.
The base URL defaults to DefaultBaseURL, overridden by MAILTEA_API_BASE_URL and then by WithBaseURL.
type ContactPropertiesService ¶
type ContactPropertiesService struct {
// contains filtered or unexported fields
}
ContactPropertiesService is the `contact_properties` resource — custom contact fields. Reach it as client.ContactProperties.
Definitions are team-scoped: there is no publication_id here. Create takes key and type ("string" or "number").
type ContactsService ¶
type ContactsService struct {
// contains filtered or unexported fields
}
ContactsService is the `contacts` resource. Reach it as client.Contacts.
Audience resources are scoped to a publication — every call takes a publication_id.
func (*ContactsService) Create ¶
func (s *ContactsService) Create(ctx context.Context, request CreateContactRequest) (Object, error)
Create adds a contact — or updates it if the email already exists in the publication, because the endpoint upserts. Upsert is the same call under the name of what it actually does.
func (*ContactsService) Delete ¶
func (s *ContactsService) Delete(ctx context.Context, idOrEmail string, params Params) (Object, error)
Delete removes a contact. Requires publication_id.
func (*ContactsService) List ¶
List lists contacts, cursor-paginated. Filters: publication_id (required), status (active/unsubscribed/suppressed), search (matches the email address), limit, after (a cursor from a previous next_cursor).
func (*ContactsService) Update ¶
func (s *ContactsService) Update(ctx context.Context, idOrEmail string, request UpdateContactRequest) (Object, error)
Update changes a contact. The publication is sent in the query string as well as the body, which is what this endpoint reads.
func (*ContactsService) Upsert ¶
func (s *ContactsService) Upsert(ctx context.Context, request CreateContactRequest) (Object, error)
Upsert creates the contact or updates it in place — an alias of Create, named for what POST /v1/contacts really does.
type CreateContactRequest ¶
type CreateContactRequest struct {
PublicationID string `json:"publication_id"`
Email string `json:"email"`
Status string `json:"status,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
CreateContactRequest is the body of POST /v1/contacts.
Status is one of "active", "unsubscribed" or "suppressed"; omit it and the server picks the default.
func (CreateContactRequest) MarshalJSON ¶
func (r CreateContactRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type CreatePostRequest ¶
type CreatePostRequest struct {
PublicationID string `json:"publication_id"`
Subject string `json:"subject,omitempty"`
Name string `json:"name,omitempty"`
Kind string `json:"kind,omitempty"`
HTML string `json:"html,omitempty"`
Text string `json:"text,omitempty"`
TemplateID string `json:"template_id,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
From string `json:"from,omitempty"`
ReplyTo string `json:"reply_to,omitempty"`
// Send delivers the post to the audience as part of creating it.
Send bool `json:"send,omitempty"`
// ScheduledAt, with Send, queues that delivery for later (ISO 8601).
ScheduledAt string `json:"scheduled_at,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
CreatePostRequest is the body of POST /v1/posts.
Seed the post from a published server template with TemplateID + Variables, or pass inline HTML. Kind selects the post type ("newsletter" or "broadcast"). Set Send to deliver right after creating (or add ScheduledAt to schedule) — that requires the `issues:send` scope.
func (CreatePostRequest) MarshalJSON ¶
func (r CreatePostRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type CreateTopicRequest ¶
type CreateTopicRequest struct {
PublicationID string `json:"publication_id"`
Name string `json:"name"`
DefaultSubscription string `json:"default_subscription"`
Description string `json:"description,omitempty"`
Visibility string `json:"visibility,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
CreateTopicRequest is the body of POST /v1/topics.
DefaultSubscription is required and is one of "opt_in" or "opt_out". Visibility defaults to "private"; "public" makes the topic appear on the reader preference page as its own subscription.
func (CreateTopicRequest) MarshalJSON ¶
func (r CreateTopicRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type DomainsService ¶
type DomainsService struct {
// Tracking covers the CNAME tracking sub-domains under a domain.
Tracking *TrackingDomainsService
// contains filtered or unexported fields
}
DomainsService is the `domains` resource — email and site sending domains. Reach it as client.Domains.
Scoped to a publication. Register a domain, add the DNS `records` the response lists, then Verify it before sending from it.
func (*DomainsService) Create ¶
Create registers a domain. The response's `records` lists the DNS records to add.
func (*DomainsService) Update ¶
Update changes a domain — open_tracking, click_tracking, custom_return_path and the like. publication_id travels in the query string and the body alike.
custom_return_path delegates a subdomain as the envelope sender so SPF aligns with your own domain. Mail keeps sending on the default return-path until the delegated DNS resolves.
type DroppedRecipient ¶
type DroppedRecipient struct {
Address string `json:"address"`
Field string `json:"field"`
Reason string `json:"reason"`
}
DroppedRecipient is an address the message did not reach, and why. To/CC/BCC are what was ASKED for; a suppressed or unusable address is filtered out of the envelope but left in those fields, so without reading this a partially delivered send looks identical to a fully delivered one.
type Email ¶
type Email struct {
Object string `json:"object"`
ID string `json:"id"`
From string `json:"from"`
To string `json:"to"`
CC string `json:"cc"`
BCC string `json:"bcc"`
ReplyTo string `json:"reply_to"`
Subject string `json:"subject"`
HTML string `json:"html"`
Text string `json:"text"`
// LastEvent is where the send got to: queued, scheduled, sent, delivered,
// delivery_delayed, bounced, complained, failed, suppressed, canceled.
// Reading it is how you check on a send without setting up a webhook.
LastEvent string `json:"last_event"`
// Status is a friendly alias of LastEvent, filled in by this SDK.
Status string `json:"status"`
// Error is why the send failed, in neutral words — the provider's own
// wording is never returned. Empty on every email that has not failed.
Error string `json:"error"`
DroppedRecipients []DroppedRecipient `json:"dropped_recipients"`
CreatedAt string `json:"created_at"`
ScheduledAt string `json:"scheduled_at"`
FailedAt string `json:"failed_at"`
DelayedAt string `json:"delayed_at"`
OpenedAt string `json:"opened_at"`
OpenCount int `json:"open_count"`
ClickedAt string `json:"clicked_at"`
ClickCount int `json:"click_count"`
Tags []Tag `json:"tags"`
Headers map[string]string `json:"headers"`
Attachments []AttachmentMeta `json:"attachments"`
}
Email is one send, as returned by Get.
type EmailsService ¶
type EmailsService struct {
// Inbound covers received mail: list, get, reply, and attachments.
Inbound *InboundService
// contains filtered or unexported fields
}
EmailsService is the `emails` resource. Reach it as client.Emails.
func (*EmailsService) Analytics ¶
Analytics aggregates transactional metrics over an optional date window: totals, delivered/bounced/open/click counts, per-status counts, and rates. Optional filters: from_date, to_date (ISO 8601), clamped like List's.
func (*EmailsService) Batch ¶
func (s *EmailsService) Batch(ctx context.Context, requests []SendEmailRequest) (*BatchResponse, error)
Batch sends up to 100 emails in one request. The body is a bare array, which is what this endpoint takes — not an object wrapping one.
func (*EmailsService) Cancel ¶
Cancel stops a scheduled email before it sends.
It works only while the email is still `scheduled`. Any other status — including the `queued` of an ordinary immediate send — answers 422, so treat that as "too late to stop it" rather than as a bug. There is no DELETE on emails; cancel is this POST.
The reply is `{object, id}` and nothing more — a 2xx IS the confirmation. Call Get if you want to read the resulting `canceled` status back.
func (*EmailsService) Get ¶
Get retrieves an email with its delivery status and tracking counters.
The id goes through url.PathEscape: a real "txemail_…" passes through untouched, and an id from somewhere less trustworthy cannot walk out of the path segment it belongs in.
func (*EmailsService) List ¶
List lists emails, most recent first. Optional filters: status, tag_name, tag_value, search (substring match on recipient/sender/subject), from_date, to_date, limit, offset.
from_date is clamped to the plan's analytics retention window — 30 days on most plans, 90 on Scale and Enterprise. A value reaching further back returns data from the start of that window rather than an error, and omitting it returns the window rather than all time.
func (*EmailsService) Reschedule ¶
Reschedule is Update for the one case it exists for.
func (*EmailsService) Send ¶
func (s *EmailsService) Send(ctx context.Context, request SendEmailRequest) (*SendEmailResponse, error)
Send sends one transactional email, or schedules it when ScheduledAt is set.
func (*EmailsService) Update ¶
func (s *EmailsService) Update(ctx context.Context, id string, request UpdateEmailRequest) (Object, error)
Update changes a scheduled email (currently only scheduled_at).
type Error ¶
type Error struct {
// Status is the HTTP status code. Zero means the failure happened on this
// side of the wire — a missing API key, an unreachable host, a response
// body that was not the JSON it claimed to be — so there is no status to
// report. Branch on it before assuming the API said anything at all.
Status int
// Message is the API's own `error` field ("Domain is not verified",
// "Validation failed"), or the client-side reason when Status is 0.
Message string
// Code is the API's machine-readable code, when it sends one (for example
// `marketing_plan_required` on a 402, or `template_version_not_found`).
// Branching on Code survives a copy change to Message. Empty when absent.
Code string
// Details is the validation issue list the API returns alongside a 400,
// naming the fields that failed. Nil when absent. It is decoded as
// free-form JSON because its shape varies per endpoint; use DetailsJSON to
// print it or json.Unmarshal it into your own type.
Details interface{}
// RequestID is the response's `x-request-id` header — quote it in a support
// request and the exact call can be found.
RequestID string
// Body is the raw response body, kept verbatim. The parsed fields above are
// what you branch on; this is what you log when they were not enough.
Body string
}
Error is returned whenever the Mailtea API answers with a non-2xx status, or the client is misconfigured before a request is even attempted.
Reach it with errors.As:
var apiErr *mailtea.Error
if errors.As(err, &apiErr) && apiErr.Status == 422 {
// too late to cancel
}
type EventDefinitionsService ¶
type EventDefinitionsService struct {
// contains filtered or unexported fields
}
EventDefinitionsService is the `event_definitions` resource — the catalog of event names a publication expects, with optional property schemas. Reach it as client.EventDefinitions.
Definitions are scoped to a publication. They are documentation and tooling, not a gate: Events.Send accepts an event with no definition.
func (*EventDefinitionsService) Create ¶
Create adds an event definition. Takes publication_id and name, plus optional description and schema_json. The name is immutable once created.
func (*EventDefinitionsService) Delete ¶
func (s *EventDefinitionsService) Delete(ctx context.Context, id string, params Params) (Object, error)
Delete removes an event definition. Requires publication_id. Events already recorded under that name are untouched.
func (*EventDefinitionsService) Get ¶
func (s *EventDefinitionsService) Get(ctx context.Context, id string, params Params) (Object, error)
Get retrieves one definition. Requires publication_id. Adds schema_properties and inferred_properties — the latter computed on read over the last 500 events, reporting each key's type, sample count and COVERAGE. Low coverage is the trap: a condition on a key present in 3% of events will almost never match.
func (*EventDefinitionsService) List ¶
List lists event definitions, cursor-paginated. Filters: publication_id (required), limit, after. List items carry no inferred_properties — use Get for those.
func (*EventDefinitionsService) Update ¶
func (s *EventDefinitionsService) Update(ctx context.Context, id string, params Params) (Object, error)
Update changes a definition's description or schema_json (an explicit nil clears the schema back to free-form). publication_id is required and is sent as a query parameter ONLY. `name` is immutable — sending it is a 400 `event_name_immutable`, not a silently dropped rename.
type EventsService ¶
type EventsService struct {
// contains filtered or unexported fields
}
EventsService is the `events` resource — custom product events that trigger automations and resume `wait_for_event` steps. Reach it as client.Events.
Events are scoped to a publication.
func (*EventsService) List ¶
List lists recorded events, cursor-paginated. Filters: publication_id (required), name, contact_id, limit, after.
func (*EventsService) Send ¶
Send records an event for a contact. Takes publication_id, name, and exactly one of contact_id or email (both is a 400 `contact_reference_conflict`, neither a 400 `contact_reference_required`). Optional: create_contact, properties, occurred_at, idempotency_key.
create_contact is OPT-IN — without it an unresolvable address is a 404 `contact_not_found` rather than a new contact.
Returns 202 with enrolled_automations and resumed_runs. A replay of the same idempotency_key returns the ORIGINAL event id with replayed: true and always reports enrolled_automations: 0, resumed_runs: 0. Note that resumed_runs: 0 on a FRESH ingest does not prove nothing matched — a run being advanced concurrently is invisible for that instant, so read the run itself (client.AutomationRuns.Get) rather than the counter.
type HTTPDoer ¶
HTTPDoer is the slice of *http.Client this SDK uses. Supply your own to add a proxy, a timeout, retries, or — in a test — to answer without a network:
client, _ := mailtea.New("mt_pat_test", mailtea.WithHTTPClient(fake))
type InboundAttachmentsService ¶
type InboundAttachmentsService struct {
// contains filtered or unexported fields
}
InboundAttachmentsService covers attachments on a received email. Reach it as client.Emails.Inbound.Attachments. Each returned object carries a short-lived signed download_url.
type InboundService ¶
type InboundService struct {
// Attachments covers the files on a received email.
Attachments *InboundAttachmentsService
// contains filtered or unexported fields
}
InboundService covers inbound (received) emails. Reach it as client.Emails.Inbound.
List and retrieve mail delivered to your receiving domains, download attachments, and Reply — which threads correctly by construction and reuses the transactional send pipeline. Scoped to a publication: pass publication_id to List.
func (*InboundService) Get ¶
Get retrieves a single received email, including its body, headers, and attachments.
func (*InboundService) List ¶
List lists received emails in a publication, most recent first, cursor-paginated. Takes publication_id, optional limit (1-100, default 20) and cursor.
func (*InboundService) Reply ¶
Reply replies to a received email. The reply target (`to`), threading headers, and the "Re: " subject default are all server-derived — pass only the content (html/text, and optionally from, subject, cc, bcc, idempotency_key). Returns the resulting transactional email's id and status.
type List ¶
type List struct {
Object string `json:"object"`
Data []Object `json:"data"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor"`
}
List is the API's standard list envelope. Offset-paginated endpoints (emails, posts) fill Total/Limit/Offset/HasMore; cursor-paginated ones (contacts, senders, templates, automations, events, …) fill NextCursor and leave Total at zero. Data holds the rows either way.
type Object ¶
type Object map[string]interface{}
Object is a decoded JSON response. It is a plain map, so an unfamiliar or brand-new field is still readable, with typed accessors for the common reads and Decode for pulling the whole thing into a struct of your own.
func (Object) Decode ¶
Decode re-encodes the object and unmarshals it into v, so a caller who wants a struct does not have to reach through the map:
var domain struct {
ID string `json:"id"`
Status string `json:"status"`
}
err := created.Decode(&domain)
func (Object) Float ¶
Float returns a numeric field. JSON numbers decode to float64, so this is the lossless read; Int rounds it for counters and ids.
type Option ¶
type Option func(*Client)
Option configures a Client at construction.
func WithBaseURL ¶
WithBaseURL points the client at a different Mailtea — a self-hosted instance, or http://127.0.0.1:7787 in local dev. An empty string is ignored, so passing os.Getenv("MAILTEA_API_BASE_URL") is safe when the variable is unset. A trailing slash is trimmed; a path prefix is kept.
func WithHTTPClient ¶
WithHTTPClient replaces the underlying HTTP client. A nil value is ignored.
type Params ¶
type Params map[string]interface{}
Params is a free-form wire-format payload: snake_case keys exactly as the REST API names them ("reply_to", "publication_id", "scheduled_at").
The methods this SDK types explicitly — emails.Send/Batch/Update, contacts.Create/Update, posts.Create/Send/SendTest, topics.Create — take a request struct instead. Everything else takes Params, so a field added to the API is reachable the day it ships rather than the day this SDK is re-released.
type PostsService ¶
type PostsService struct {
// contains filtered or unexported fields
}
PostsService is the `posts` resource — newsletter posts and issues. Reach it as client.Posts.
func (*PostsService) Create ¶
func (s *PostsService) Create(ctx context.Context, request CreatePostRequest) (Object, error)
Create adds a newsletter post — a draft unless Send is set.
func (*PostsService) List ¶
List lists posts, most recent first, offset-paginated. Takes publication_id (required) plus optional limit, offset, status and kind.
func (*PostsService) Send ¶
func (s *PostsService) Send(ctx context.Context, id string, request SendPostRequest) (Object, error)
Send delivers a draft post to the publication's audience — immediately, or at ScheduledAt. Requires the `issues:send` scope.
func (*PostsService) SendTest ¶
func (s *PostsService) SendTest(ctx context.Context, id string, request SendTestPostRequest) (*SendTestPostResponse, error)
SendTest sends a TEST copy of a post to specific recipients, to check it before subscribers see it. It renders the post exactly as a subscriber would receive it and delivers a one-shot [TEST] email — it does NOT send to the audience.
type SegmentsService ¶
type SegmentsService struct {
// contains filtered or unexported fields
}
SegmentsService is the `segments` resource. Reach it as client.Segments.
Audience segments are scoped to a publication — pass publication_id. To clear a nullable filter on update, set it to nil explicitly (Params{"status_filter": nil} is dropped from a query but kept in a body); omit the key to leave it unchanged.
type SendEmailRequest ¶
type SendEmailRequest struct {
// From is a verified sender, e.g. "Acme <hello@acme.com>".
From string `json:"from,omitempty"`
// SenderID selects a saved sender ("snd_…") instead of From.
SenderID string `json:"sender_id,omitempty"`
To []string `json:"to"`
Subject string `json:"subject"`
HTML string `json:"html,omitempty"`
Text string `json:"text,omitempty"`
// Template renders a published template server-side. Mutually exclusive
// with HTML.
Template *TemplateRef `json:"template,omitempty"`
CC []string `json:"cc,omitempty"`
BCC []string `json:"bcc,omitempty"`
ReplyTo []string `json:"reply_to,omitempty"`
// ScheduledAt queues the send for later, ISO 8601: "2026-09-01T09:00:00Z".
ScheduledAt string `json:"scheduled_at,omitempty"`
Tags []Tag `json:"tags,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
// TrackingOpen and TrackingClick opt this message out of the open pixel or
// out of rewritten links. They are pointers because "unset" and "false" are
// different: unset means tracked, as it always has been. A sending domain
// with tracking switched off cannot be overridden from here — policy
// narrows, it never widens.
TrackingOpen *bool `json:"tracking_open,omitempty"`
TrackingClick *bool `json:"tracking_click,omitempty"`
// Extra carries wire fields this SDK version does not name yet. Its keys
// are merged over the encoded struct, so a field the API adds tomorrow is
// sendable today.
Extra Params `json:"-"`
}
SendEmailRequest is the body of POST /v1/emails.
Set the From with exactly one of From (a "Name <email>" string) or SenderID (a named, verified publication sender, which also supplies its default reply-to). Provide HTML/Text OR Template, never both.
To, CC and BCC are capped at 50 recipients COMBINED — the provider refuses a larger message, so the API rejects it rather than accepting a send that dies downstream where you cannot see it.
func (SendEmailRequest) MarshalJSON ¶
func (r SendEmailRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type SendEmailResponse ¶
SendEmailResponse is what a send returns: the id you look the send up by, and the id webhooks reference.
type SendPostRequest ¶
type SendPostRequest struct {
ScheduledAt string `json:"scheduled_at,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
SendPostRequest is the body of POST /v1/posts/{id}/send. Leave it zero to send now; set ScheduledAt (ISO 8601) to schedule.
func (SendPostRequest) MarshalJSON ¶
func (r SendPostRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type SendTestPostRequest ¶
type SendTestPostRequest struct {
Recipients []string `json:"recipients"`
From string `json:"from,omitempty"`
ReplyTo string `json:"reply_to,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
SendTestPostRequest is the body of POST /v1/posts/{id}/test. Up to 10 recipients; From must use a verified domain.
func (SendTestPostRequest) MarshalJSON ¶
func (r SendTestPostRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type SendTestPostResponse ¶
type SendTestPostResponse struct {
SentTo []string `json:"sent_to"`
FailedTo []string `json:"failed_to"`
}
SendTestPostResponse reports which test recipients were reached.
type SendersService ¶
type SendersService struct {
// contains filtered or unexported fields
}
SendersService is the `senders` resource — named From identities. Reach it as client.Senders.
Senders are scoped to a publication. Create takes name and email (the address must live on a verified, DKIM-verified email domain), plus optional reply_to and is_default. The email is immutable, so Update only changes name, reply_to and is_default.
type SuppressionsService ¶
type SuppressionsService struct {
// contains filtered or unexported fields
}
SuppressionsService is the `suppressions` resource — the team-wide do-not-send list. Reach it as client.Suppressions.
Suppressions are team-scoped: there is no publication_id here.
func (*SuppressionsService) Add ¶
Add adds addresses to the suppression list. Takes emails (up to 1000) and an optional reason. Returns {"added": n}.
func (*SuppressionsService) Export ¶
func (s *SuppressionsService) Export(ctx context.Context) (string, error)
Export returns the whole suppression list as CSV — the raw text/csv body (email,reason,source,created_at with a header row), not JSON.
type TemplateRef ¶
type TemplateRef struct {
ID string `json:"id"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
TemplateRef seeds a send from a published server-side template instead of inline HTML. Variables fill the template's placeholders.
type TemplatesService ¶
type TemplatesService struct {
// contains filtered or unexported fields
}
TemplatesService is the `templates` resource — reusable server-side email templates. Reach it as client.Templates.
Templates are scoped to a publication (except Render, which just renders a spec). Create one from raw html, a json-render spec, or an editor_doc (a Studio editor design), then Publish it before seeding posts or emails from it.
func (*TemplatesService) Create ¶
Create adds a template from html, a spec, OR an editor_doc — exactly one is required, and the server renders html from an editor_doc, so do not send both. Takes publication_id and name, plus optional style_profile, mailtea_theme, global_css, category, preview_image_url, tags, description, text, subject, from, reply_to and variables.
func (*TemplatesService) Duplicate ¶
Duplicate copies a template into a new draft. Requires publication_id.
func (*TemplatesService) List ¶
List lists templates, cursor-paginated. Filters: publication_id (required), limit, after (a cursor from a previous next_cursor).
func (*TemplatesService) Publish ¶
Publish makes a template available to seed posts and emails. Requires publication_id.
func (*TemplatesService) Render ¶
Render renders a json-render `spec` (with optional `variables`) to HTML without creating a template. Returns {"html": ..., "text": ...}.
func (*TemplatesService) RestoreVersion ¶
func (s *TemplatesService) RestoreVersion(ctx context.Context, id string, version interface{}, params Params) (Object, error)
RestoreVersion puts an older design from Versions back onto the template. Requires publication_id.
Restoring is a content write, so THE TEMPLATE RETURNS TO DRAFT — automations and the API stop sending it until Publish is called again. The reply's `unpublished` reports whether that just happened; re-publishing is the caller's job.
History is forward-only: the design being replaced is recorded as its own version first, then the restored design is appended as the new newest one. Nothing is rewound or deleted, so a restore is itself undone by restoring the entry directly above it.
Restoring the design that is already current writes nothing and returns restored: false with reason: "identical" and unpublished: false, so a no-op restore cannot unpublish a live template. A version that has aged out of retention returns a *Error with Code "template_version_not_found".
version is an int or a string — whatever Versions reported.
func (*TemplatesService) Unpublish ¶
Unpublish returns a published template to draft. published_at is kept — it records that the template was published once, not that it still is. Requires publication_id.
func (*TemplatesService) Update ¶
Update changes a template. An editor_doc re-renders html server-side, so do not send both. global_css, category, preview_image_url, tags, text, subject, from and reply_to accept an explicit nil to clear them. publication_id is required and travels in the query string as well as the body.
func (*TemplatesService) Versions ¶
Versions lists a template's design history, newest first. Requires publication_id; optional limit (the server caps it at the retained maximum).
Entries are metadata only — version, origin ("edit", "publish" or "restore"), restored_from_version, format, name, sealed, is_current, created_at, updated_at and author — never the design document, which one entry alone can carry half a megabyte of. is_current marks the design the template is serving right now, which is not always the newest entry: a metadata-only update touches the template without recording a version.
The reply also carries `retention`: only the newest max_versions are kept, and consecutive edits by the same author within coalesce_window_seconds collapse into one entry.
type TopicsService ¶
type TopicsService struct {
// contains filtered or unexported fields
}
TopicsService is the `topics` resource — topic definitions. Reach it as client.Topics.
Topics are scoped to a publication. This manages topic definitions only; assigning topics to contacts is not yet exposed by the API.
func (*TopicsService) Create ¶
func (s *TopicsService) Create(ctx context.Context, request CreateTopicRequest) (Object, error)
Create adds a topic definition.
type TrackingDomainsService ¶
type TrackingDomainsService struct {
// contains filtered or unexported fields
}
TrackingDomainsService covers tracking sub-domains (CNAME) under a domain — used to serve open-pixel and click-tracking links from your own domain. Reach it as client.Domains.Tracking.
func (*TrackingDomainsService) Create ¶
func (s *TrackingDomainsService) Create(ctx context.Context, domainID string, params Params) (Object, error)
Create adds a tracking sub-domain. Takes publication_id and subdomain. The response's `records` lists the CNAME to add.
publication_id goes in the query string and only `subdomain` in the body, which is what this endpoint reads.
func (*TrackingDomainsService) Delete ¶
func (s *TrackingDomainsService) Delete(ctx context.Context, domainID, trackingDomainID string, params Params) (Object, error)
Delete removes a tracking sub-domain. Requires publication_id.
type UpdateContactRequest ¶
type UpdateContactRequest struct {
PublicationID string `json:"publication_id"`
Status string `json:"status,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
UpdateContactRequest is the body of PATCH /v1/contacts/{id_or_email}. PublicationID is required — it goes in the query string and the body alike.
func (UpdateContactRequest) MarshalJSON ¶
func (r UpdateContactRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type UpdateEmailRequest ¶
type UpdateEmailRequest struct {
ScheduledAt string `json:"scheduled_at,omitempty"`
// Extra carries wire fields this SDK version does not name yet.
Extra Params `json:"-"`
}
UpdateEmailRequest reschedules a scheduled email. ScheduledAt is currently the only field the API accepts.
func (UpdateEmailRequest) MarshalJSON ¶
func (r UpdateEmailRequest) MarshalJSON() ([]byte, error)
MarshalJSON merges Extra over the named fields.
type WebhooksService ¶
type WebhooksService struct {
// contains filtered or unexported fields
}
WebhooksService is the `webhooks` resource — outbound event subscriptions. Reach it as client.Webhooks.
Scoped to a publication. Create returns the signing_secret ONCE; store it and verify deliveries with VerifyWebhookSignature.