You need: email composed and provisioned.
Eight event types
The template model is what declares a link as trackable, the job model is the row events attach to, bounces and suppression is what a complaint becomes, and deliverability is what the numbers are telling you.
| Event | Written by |
|---|---|
sent | The send Workflow |
open | The tracking pixel |
click | A tracked link |
unsubscribe | The opt-out callback |
bounce, complaint | The inbound handler |
suppressed | A send that was skipped |
failed | A terminal send error |
They live in your own database, keyed on the same normalized recipient the job is — so a job and its events agree on who the person is.
Per-campaign stats are one query
Pass a campaignId on any send and a single grouped query gives you the whole funnel — sent, opened, clicked, bounced, complained, unsubscribed.
Types with no events come back as zero rather than absent, derived from the enum so a new event type cannot be forgotten.
Links are rewritten from a declaration
A template names the payload locations that hold URLs, each with a label, and the engine rewrites those to the tracking callback. The label is what lands on the click event for attribution.
A location is a top-level key — "url" — or a field on each element of an array — "articles[].link".
The callbacks keep a fixed prefix
/_pithy/email/c/:token · /_pithy/email/o/:token · /_pithy/email/u/:token
They keep /_pithy/email whatever basePath says, because a tracking URL is minted into mail already sitting in somebody’s inbox — and moving the route breaks every link ever sent.
All three are public, gated by the signature on the token in the path.
Unsubscribe answers both verbs
A one-click unsubscribe header sends a POST; a person clicking the link sends a GET.
Refusing either refuses somebody trying to leave.
The cross-environment limit
Links point at the sending environment’s Worker, so a production campaign’s opens, clicks and unsubscribes land in production’s database.
Bounces do not follow that rule. There is one inbound Worker per domain, and it is production’s — so feature and staging environments see their own opens, clicks and unsubscribes but not asynchronously-routed bounces.
A platform constraint stated rather than discovered.
Tracking defaults follow the category
category drives them — a marketing template and a transactional one do not want the same defaults, and the template says which it is.
What not to put in a link label
It lands on an event row and it is queryable. A label is for attribution — "cta", "article-3" — not for anything about the recipient.