---
title: "Subscription topics"
description: "Let contacts opt out of one kind of mail without leaving your list."
url: https://www.unitpost.com/docs/topics
section: Docs
updated: 2026-07-23
---
# Subscription topics

## Subscription topics

> Let contacts opt out of one kind of mail without leaving your list.

A topic is a subscription category — "Product updates," "Promotions" — that a contact can opt out of. It's independent of the global unsubscribe: a contact can stay on your list but silence one kind of mail.

### Opt-in vs. opt-out

The default_opt_in field decides what happens for a contact who has never touched the topic:

- default_opt_in: true (the default) — an opt-out topic. Contacts are auto-enrolled unless they opt out.
- default_opt_in: false — an opt-in topic. Contacts are silent until they explicitly subscribe.

> **An explicit choice always wins:** Once a contact subscribes or unsubscribes from a topic, that explicit choice overrides the default in both directions. The default only governs contacts with no recorded preference.

### Managing topics

Create, list, update, and archive topics under /api/v1/topics. Deleting a topic archives it (soft): it disappears from pickers, but campaigns that used it keep their history.

**cURL**

```bash
# Create an opt-out newsletter topic
curl https://www.unitpost.com/api/v1/topics \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Product updates", "default_opt_in": true }'

# Opt a contact out of it (id or email in the path)
curl -X PUT https://www.unitpost.com/api/v1/contacts/customer@acme.com/topics \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "topic_id": "top_123", "subscribed": false }'
```

**Node.js**

```ts
// Create a topic
const { id } = await fetch("https://www.unitpost.com/api/v1/topics", {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Product updates", default_opt_in: true }),
}).then((r) => r.json());

// Read a contact's effective subscriptions, then opt them out of one
await fetch("https://www.unitpost.com/api/v1/contacts/customer@acme.com/topics", {
  method: "PUT",
  headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ topic_id: id, subscribed: false }),
});
```

**Python**

```python
# Create a topic
topic = requests.post(
    "https://www.unitpost.com/api/v1/topics",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"name": "Product updates", "default_opt_in": True},
).json()

# Opt a contact out of it (id or email in the path)
requests.put(
    "https://www.unitpost.com/api/v1/contacts/customer@acme.com/topics",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"topic_id": topic["id"], "subscribed": False},
)
```

### Per-contact preferences

GET /api/v1/contacts/{id}/topics returns a contact's effective subscription for every topic — folding in each topic's default so you get the real answer, not just rows they've touched. PUT the same path with { topic_id, subscribed } to set one. The path accepts a contact id or email; an unknown topic returns 404. This is the same thing a contact does by toggling a topic on the unsubscribe preference page.

### Scoping a campaign

Pass topic_id when you create a campaign to scope it. At send, contacts opted out of that topic are skipped (and counted as suppressed in the validate report) — on top of the usual segment-membership and global-unsubscribe checks. You can't point a campaign at an archived or missing topic: create and edit return a 409, and validate/send report a TOPIC_ARCHIVED or TOPIC_MISSING blocker.

> **One-click unsubscribe respects the topic:** Mail sent for a topic-scoped campaign carries a topic-aware unsubscribe link and RFC 8058 List-Unsubscribe-Post header, so a recipient's one-click opt-out silences just that topic rather than all of your marketing.

## Related

- [Sending domains](https://www.unitpost.com/docs/domains): Add SPF, DKIM, and DMARC so mail sends from your domain.
- [Open & click tracking](https://www.unitpost.com/docs/tracking): Control engagement tracking per send, template, or category.
- [Batch sending](https://www.unitpost.com/docs/batch): Send up to 100 distinct messages — schedule and cancel as a unit.
