Skip to content

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.

typescript
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>',
});
POST
/subscription-topics

Create a new subscription topic.

json
{
  "name": "Product Updates",
  "description": "New features, improvements, and release notes"
}
GET
/subscription-topics

List all subscription topics.

json
{
  "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
    }
  ]
}
DELETE
/subscription-topics/:id

Delete 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.

json
{
  "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}}.

GET
/subscription-topics/contacts/:contactId/subscriptions

List all topic subscriptions for a specific contact.

json
{
  "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", ... }
    }
  ]
}
POST
/subscription-topics/contacts/:contactId/subscriptions

Subscribe a contact to a topic. Both contactId in the path and topic_id in the body are publicId strings (con_*, top_*), not integers.

json
{
  "topic_id": "top_xyz789ghi012"
}
DELETE
/subscription-topics/contacts/:contactId/subscriptions/:topicId

Unsubscribe a contact from a topic. The record is kept with subscribed set to false.

json
{
  "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.

bash
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" }'

Related