Subscription Topics
Let contacts manage their email preferences with subscription topics. Instead of a binary subscribe/unsubscribe, contacts can opt into specific categories like “Product Updates” or “Marketing”.
How topics filter broadcasts
Topics are opt-out: a contact with no subscription record for a topic counts as subscribed, and only an explicit subscribed: false (from the preference center, the unsubscribe endpoint below, or the dashboard) opts them out. Pass topic_id when creating or updating a broadcast and the send skips every contact in the segment who opted out of that topic. In the dashboard, pick the topic on the broadcast form, and toggle a contact's topics on their detail page.
await poststack.broadcasts.create({
segment_id: 'seg_abc123def456',
topic_id: 'top_xyz789ghi012', // contacts opted out of this topic are skipped
from: 'Acme <news@acme.io>',
subject: 'Product updates',
html: '<p>...</p>',
});/subscription-topicsCreate a new subscription topic.
{
"name": "Product Updates",
"description": "New features, improvements, and release notes"
}/subscription-topicsList all subscription topics.
{
"topics": [
{
"id": 1,
"publicId": "top_xyz789ghi012",
"name": "Product Updates",
"description": "New features, improvements, and release notes",
"createdAt": "2026-03-23T10:00:00.000Z",
"subscriberCount": 1204
},
{
"id": 2,
"publicId": "top_jkl345mno678",
"name": "Marketing",
"description": "Promotions, offers, and company news",
"createdAt": "2026-03-20T10:00:00.000Z",
"subscriberCount": 877
}
]
}/subscription-topics/:idDelete a subscription topic by its public id (top_…). Every contact's subscription record for the topic is removed with it; the contacts themselves are not deleted.
{
"success": true
}Managing Contact Subscriptions
Manage which topics a specific contact is subscribed to. Endpoints are organized under the contact's public id. A contact with no record for a topic counts as subscribed; unsubscribing writes a record with subscribed: false. Contacts can also change these themselves from the preference centre linked by {{preferences_url}}.
/subscription-topics/contacts/:contactId/subscriptionsList all topic subscriptions for a specific contact.
{
"subscriptions": [
{
"id": 501,
"contactId": 9120,
"topicId": 1,
"subscribed": true,
"updatedAt": "2026-03-23T10:00:00.000Z",
"topic": { "id": 1, "publicId": "top_xyz789ghi012", "name": "Product Updates", ... }
},
{
"id": 502,
"contactId": 9120,
"topicId": 2,
"subscribed": false,
"updatedAt": "2026-03-24T08:00:00.000Z",
"topic": { "id": 2, "publicId": "top_jkl345mno678", "name": "Marketing", ... }
}
]
}/subscription-topics/contacts/:contactId/subscriptionsSubscribe a contact to a topic. Both contactId in the path and topic_id in the body are publicId strings (con_*, top_*), not integers.
{
"topic_id": "top_xyz789ghi012"
}/subscription-topics/contacts/:contactId/subscriptions/:topicIdUnsubscribe a contact from a topic. The record is kept with subscribed set to false.
{
"subscription": {
"id": 501,
"contactId": 9120,
"topicId": 1,
"subscribed": false,
"updatedAt": "2026-03-25T09:00:00.000Z"
}
}More Topic Endpoints
GET /subscription-topics/:id returns one topic, PATCH /subscription-topics/:id updates its name or description, and GET /subscription-topics/:id/subscribers pages through the contacts with a record for the topic. Writes are limited to 30 requests per minute.
curl -X PATCH https://api.poststack.dev/subscription-topics/top_xyz789ghi012 \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "description": "Release notes, roughly twice a month" }'