Two questions, never one
Neither is derivable from the other, which is why both are declared. A receipt is
a courtesy and is not marketing: nobody unsubscribes from the answer to
something they did with their own address. A campaign is marketing and is not
free: it costs a send like everything else.
Three categories, and transactional is not one of them
product_news is separate from marketing on purpose. Somebody who wants no
offers may still want to know their tools changed, and one switch for both loses
that person entirely. surveys is separate because the cost to the recipient is
different in kind — an announcement is read, an NPS prompt wants work.
There is no transactional category, and there never will be. A verification
link, a password reset, a receipt, a support reply and a dunning notice keep
arriving after somebody switches every one of the three off. That is not a rule
applied at send time: the value does not exist, so there is no row a settings
screen, a bulk update or a mistake could write to stop one.
The class ranks messages, never moments
Free authentication is only free if the mail behind it is, so an environment has a monthly email allowance. Exceeding it may never block a sign-in — and a message that does not go out is a sign-up that does not complete, so “never block” cannot mean “never withhold” either. It is resolved by ranking the messages:
You do not set this. It is what the message is, and campaigns are
courtesy —
a campaign withheld for budget is a campaign, while a magic link withheld for
budget is a sign-in refused with an extra step in front of it.
Consent is honoured at the send, in one statement
The delivery record and the consent check are the same write. A message somebody switched off is never composed rather than composed and filtered, and two sends racing on the same contact cannot both read “has not opted out” and both go out. For a campaign, the refusal is visible: the send is recorded assuppressed with
the reason, which is what lets support answer why didn’t they get it with a fact.
An opt-out that lands between a send being claimed and the message being built
still stops it — the funnel checks again.
The unsubscribe link
Every non-transactional message carries the link in its footer and theList-Unsubscribe / List-Unsubscribe-Post header pair on its envelope. No
transactional message carries either: a receipt offering to stop sending receipts
is an offer this product does not make.
The link lands on a hosted page that names your product, because the tenant
travels in the link beside the token. Behind it:
GET is a read and the POST is the act. Mail gateways and security
scanners fetch every link in a message before a person sees it, so a GET that
unsubscribed would unsubscribe people who never clicked.
Neither takes a credential of any kind — the uk_ns_… token in the path is the
whole of it. That is the requirement rather than a gap: the reader clicked from an
inbox on a device they are not signed in on, and RFC 8058’s one-click unsubscribe
is performed by the mail client with no browser at all. A descadastro that
first asks somebody to remember a password is a descadastro that fails, and its
next step is the spam button.
For the same reason the token never expires and is never spent. The mail
client posting on the reader’s behalf and then the reader clicking the same link
are the two callers that actually happen, and both have to end in the same place.
One link switches off one category for one contact, and it is reversible from
the same page — {"opted_out": false} turns it back on. It never reveals the
address it was sent to: a token out of a forwarded email must not become a lookup
of whose inbox it came from.
The person’s own screen
label is the category as a person reads it, and the customer-facing copy this
platform ships is Portuguese today — render your own wording if your users read
something else.
The answer is always the whole catalogue, not the decisions that happen to be
stored: the absence of a stored decision is consent, so a screen rendering only
what exists would be an empty settings page for everybody who never opened it.
Transactional mail is not listed, because there is nothing here that could stop it.
A category this API does not know is refused by name, transactional
included. A consent decision dropped in silence is a screen that says saved over
a preference that was never stored, and the person finds out from the next email.
This is the one part of the contact’s self-administration surface that does not
demand a verified session. The worst an unproven session can do here is switch a
category off for the contact it named — exactly what the token in that contact’s
own inbox already does with no session at all. Demanding more proof for the in-app
control than for the emailed one would be the wrong way round.
An opt-out publishes a fact internally and is deliberately not delivered as a
webhook. It is the one withheld type whose reason is about
direction rather than permissions: what anybody would build on “this person
unsubscribed” is a message from their own system, which is the one message the
fact says must not be sent.
What this platform sent one person
customers:read rather than
messaging:manage: composing a campaign and answering did the reset arrive? are
different jobs, and the second belongs to whoever may already see the person.
Read tracked before you read the four timestamps. When it is false,
nothing was ever going to report on that message — no delivery webhook is
configured for the sending domain — so delivered_at, opened_at, clicked_at
and bounced_at are null because nobody measured them, not because the message
failed. Render that as not tracked. When it is true, null means not yet.
Permissions
messaging:manage — owner and admin — covers templates and campaigns, reads
included: an unsent price change is not something a wider permission should be
able to read. The delivery log above is customers:read, and nothing here is
gated by a plan except arming a campaign.