# Topics and the preference centre

Let people switch off product updates without switching off their receipts.

A topic is a named category of mail: product updates, a monthly digest, password resets. File a send under one and the recipient can turn that category off on its own, instead of choosing between hearing everything and hearing nothing.

This is the difference between an unsubscribe that costs you one newsletter and an unsubscribe that costs you the ability to send somebody their receipts.

### Create a topic

POST /v1/topics:
```bash
curl -X POST https://emails.sh/v1/topics \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "product-updates",
    "name": "Product updates",
    "default_opt_in": true
  }'
```

The key is what a send names and what sits inside opt-out links that are already sitting in mailboxes, so it cannot be changed afterwards. The name is what a recipient reads on the preference page and is free to change.

| Field | What it means |
| --- | --- |
| default_opt_in | What silence means. true sends to anybody who has not said no. false sends only to people who have said yes, and is the only correct setting for anything a regulator would call marketing. |
| required | Cannot be switched off. Password resets and receipts are not marketing, and the preference page shows them without a switch. |

### Send under a topic

POST /v1/emails with a topic:
```bash
curl -X POST https://emails.sh/v1/emails \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "to": ["ada@example.com"],
    "subject": "What changed in July",
    "html": "<p>Three new things.</p>",
    "topic": "product-updates"
  }'
```

Three things happen because that field is there. The recipient is checked before anything is sent, and a send to somebody who opted out is refused with 422 topic_opt_out rather than delivered. The message gets a List-Unsubscribe header pointing at a one-click opt-out for that topic alone. And a footer line is added with that link beside a link to the full preference page.

The unsubscribe link is never rewritten by click tracking. A one-click unsubscribe behind a redirect breaks RFC 8058, which is the rule Gmail and Yahoo cite in their bulk sender requirements.

### How the answer is decided

One rule, checked in a fixed order, for every send and every recipient: a suppression, then required, then what the person said, then the topic default.

- **A suppression beats everything**: A hard bounce, a spam complaint, or a workspace-wide unsubscribe stops the send whatever anybody opted into, including a required topic. That is 422 recipient_suppressed.
- **A required topic cannot be opted out of**: Somebody with an opt-out row against a required topic still receives it. There is no consent state in which withholding a password reset is a service.
- **A stated preference is obeyed**: Whichever way they said it, that is the answer. Silence falls through to the topic default.

### The preference centre

Every topic-filed message carries a link to a hosted page where the recipient sees every live topic and answers all of them at once. It is signed per address, so a link issued to one person cannot be replayed for another, and required topics appear without a switch.

Preferences are keyed by email address rather than by contact, so somebody who has never been in an audience still gets a working opt-out. Read and write them from your own code as well.

Record an opt-out from your own settings page:
```bash
curl -X POST https://emails.sh/v1/topics/preferences \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@example.com",
    "topic": "product-updates",
    "subscribed": false
  }'
```

Leave topic off a send that is genuinely transactional and it goes out governed by the suppression list alone, as before. Adding a topic to a receipt only makes sense if you mark that topic required.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
