# Let recipients unsubscribe

> The hosted unsubscribe page, RFC 8058 one-click unsubscribe, the preference center and double opt-in confirmation, with their signed tokens and what each records.

Source: https://docs.minket.com/preferences/
Updated: 2026-10-29

The platform hosts the pages recipients use to leave or adjust a list, so your app does not have to. Every page writes to the [consent ledger](https://docs.minket.com/audience/#topics-and-consent) and, where it applies, to the suppression list, so the next send already knows.

| Page | Method | What it does |
|---|---|---|
| `/unsubscribe/<token>` | GET | Shows the address and what it will stop receiving, with an Unsubscribe button and a link to the preferences. Changes nothing: link scanners and mail clients prefetch GETs. |
| `/unsubscribe/<token>` | POST | Unsubscribes. The form's button posts here, and so does a mailbox provider's one-click request. |
| `/preferences/<token>` | GET, POST | The preference center: one checkbox per topic and one for all marketing email; saving records each change. |
| `/optin/<token>` | GET | Shows the address and what it will receive, with a Confirm subscription button. Changes nothing, for the same reason. |
| `/optin/<token>` | POST | Confirms a [double opt-in](https://docs.minket.com/audience/#double-opt-in). The button posts here. |

## One-click unsubscribe

Marketing mail, and any mail to a contact, carries the platform's pair of RFC 8058 headers, which Gmail and Yahoo turn into an Unsubscribe button:

```text
List-Unsubscribe: <https://links.minket.com/unsubscribe/eyJ2IjoxLCJrIjoidW5zdWJzY3JpYmUi...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
```

When the recipient clicks it, the provider POSTs `List-Unsubscribe=One-Click` to that URL. The answer is `200` with a short text body and never a redirect, and the contact is unsubscribed ([what each page records](https://docs.minket.com/preferences/#what-each-page-records)). When the email was sent for a topic, only that topic is unsubscribed. The page acts on the reader its token names and on nobody else: the contact, or, for a recipient who is not a contact, the email's one recipient (marketing mail goes to [one reader per email](https://docs.minket.com/sending/#streams)), whose address is suppressed on the email's stream. A link from an email with several recipients names no single reader, so it shows "Link not valid" and changes nothing.

A `List-Unsubscribe` header in your request is kept and the platform adds neither of its own, since two would be malformed; send `List-Unsubscribe-Post` with it if your link handles one-click posts. Only the https form is added, never a `mailto:` one.

## What each page records

| Change | Ledger | Suppression |
|---|---|---|
| Unsubscribe button on `/unsubscribe` | `unsubscribe`, source `list_unsubscribe` | marketing stream, `reason = unsubscribe` |
| One-click POST | `unsubscribe`, source `one_click` | marketing stream, `reason = one_click` |
| Unsubscribe link for a topic | `unsubscribe` with the topic | none: sends for the topic skip the contact |
| Preference center, a topic | `subscribe` or `unsubscribe` with the topic, source `preference_center` | none |
| Preference center, all marketing email | `unsubscribe` or `resubscribe`, source `preference_center` | added, or lifted on resubscribe |
| Opt-in confirmation | `double_optin_confirmed`, source `double_opt_in` | an unsubscribe suppression is lifted |

Each entry keeps the client's address and user agent as evidence. Changes made on these pages are `contact.updated` events.

## Tokens

Every link carries a signed token, `base64url(payload).base64url(signature)`, signed with the project's signing secret (HMAC-SHA256). The payload names the project and ids only, never an address or a URL:

| Field | Meaning |
|---|---|
| `k` | The kind: `unsubscribe`, `preferences` or `optin`. A token works on its own page only. |
| `p` | The project. |
| `c` | The contact. |
| `e` | The email (List-Unsubscribe links). |
| `t` | The topic, when the link is about one. |
| `x` | The expiry: 400 days for unsubscribe and preference links, 72 hours for opt-in links. |

Tokens are checked against the project's current secret and, after a rotation, its previous one, so links in mail already delivered keep working. A rotation (owners and admins, for a secret that may have leaked) keeps the secret it replaces as the previous one, so a link stops working at the second rotation after it was signed. A token that is malformed, signed with another secret, of another kind or expired shows a "Link not valid" page with status `404`.

## The pages themselves

The pages are plain HTML with the project's name, readable without scripts or images, labelled for screen readers, and following the reader's light or dark setting. They send `X-Robots-Tag: noindex` and a Content-Security-Policy that allows no scripts, no frames (`frame-ancestors 'none'`) and forms that post back to the same page only. Each client address may make 120 requests a minute across the links host, tracking included; beyond that the answer is `429` with `Retry-After`. Request logs record nothing about these paths, the tokens or the recipients.

## In your own emails

Broadcasts and automations put an unsubscribe link and a preferences link in every marketing email they send. Marketing mail sent through the API must carry an unsubscribe link in its body (the send is refused without one); point it at your own page, or keep the headers above as the way out and link to your preference page. Every hosted page and both headers work whichever you choose.
