Skip to main content
Everything sent to one of your users — a verification link, a receipt, a win-back, a release note — goes through one funnel, and every message declares two things there. They look like one field and they are not.

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. 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 as suppressed 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. Every non-transactional message carries the link in its footer and the List-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:
The 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

One ordered read over the three things that send — campaigns, in-app notifications, and email including the transactional kind — so a password reset sits beside the marketing. It is gated with 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.