Skip to main content
Preferences are subscription topics grouped into sections. Each topic carries a default state and optional per-channel routing. You define the model in the . You read and write a user’s choices through the User Preferences API. For how these pieces combine into a delivery decision, see .

The model

A preference is an override on a topic’s default. A user falls back to the default until they make a choice. Courier then stores an override on that topic.
  • Subscription topic is a category users control independently, such as marketing or product updates. Each topic has an ID (a TypeID prefixed pt_, for example pt_01j9x8a2c4e6g8k0m2p4r6t8w0), a default state, and a section. Templates map to a topic, so every send respects the user’s choice.
  • Section groups related topics under a heading and controls which channels users can adjust for the topics inside it. Section IDs are TypeIDs prefixed ps_.
  • Default state is OPTED_IN, OPTED_OUT, or REQUIRED. It seeds every user’s status until they override it. REQUIRED can’t be overridden. See .
  • Custom routing is the set of channels a user picked for a topic: direct_message, email, inbox, push, sms, webhook. Courier stores them in custom_routing and applies them only when has_custom_routing is true.
  • Topic data is arbitrary metadata you attach to a topic through the API, useful for filtering topics or driving a custom preference UI. It’s API-only.
A user’s stored override has three parts:
  • status, either OPTED_IN or OPTED_OUT. These are the only two values a user can set.
  • custom_routing, with its has_custom_routing flag.
  • topic_id, the topic the override applies to.
On reads, status can also come back as REQUIRED. The API falls back to the topic default when the user has no override of their own, and Courier rejects a write that tries to opt a user out of a required topic.

The API surface

Two API layers touch preferences. The User Preferences API reads and writes one user’s choices. The Workspace Preferences API manages the topic and section catalog that those choices reference.

User preferences

Every endpoint accepts an optional tenant_id query parameter to scope preferences to one tenant. Both bulk endpoints accept up to 50 topics per request. No endpoint updates many users at once, so an import across your user base means one bulk call per user. For worked examples, see .

Reading current state

returns a row for every topic in every section, not only the topics the user has touched. Each row carries the topic’s default_status plus an effective status. The effective status is the user’s override when one exists, and the topic default otherwise:
Two things about this response:
  • status is effective, not stored. A topic the user never touched still returns a status, copied from default_status. A REQUIRED topic therefore reads back as "status": "REQUIRED". Branch on default_status to know whether the user chose anything.
  • Paging isn’t implemented. more is always false and no cursor is returned, so a single call gives you every topic.
The read endpoint reports the topic-level default_status, even when you pass a tenant_id. Courier applies tenant default preferences when a message sends, and doesn’t surface them here. See .

Topic and section catalog

The Workspace Preferences API manages the topics and sections themselves. List a section’s topics with . Create, retrieve, replace, or archive individual topics and sections under /preferences/sections. Most teams define these in the instead of by hand.

Digest schedules

A digest collects a topic’s messages and delivers them together on a schedule. A busy topic becomes one daily or weekly summary instead of a stream. You configure digests per subscription topic, in the or with the topic’s digest object over the API. Either way you link a digest template and define the schedule options recipients choose from. How a digest sends: when a recipient is on a digest schedule for a topic, Courier collects the topic’s messages instead of delivering each one. It renders the digest template at the scheduled time. Collected messages show a DIGESTED status in your logs. Recipients on the Instant schedule receive each message immediately, with no batching. Schedule options are the frequencies recipients can pick (up to four per topic): Instant, Daily, Weekly, Every weekday, Multiple days, or Monthly. Each scheduled option carries a time and timezone. The option marked default is where users start, or the first option when none is marked. Categories (up to five per topic) separate kinds of items within one digest, each with a retain rule (first, last, highest, or lowest ten). The digest template receives the collected items grouped by category name:
Trigger empty: by default Courier skips a scheduled digest that collected nothing. Turn on Trigger empty to send it anyway, useful when your own system supplies the data the digest renders. The API covers all of this. The digest object on and its replace counterpart sets the template, schedules, categories, and Trigger empty, and topic reads return each schedule’s schedule_id. Creating a topic with a digest object that has no schedules gets rejected. Send digest: null on a replace to turn a digest off. A user’s own schedule is digest_schedule_id on their topic preference. The API also offers and . For time-based batching inside a workflow, see the .

Audit trail

Courier records a history of preference changes. In the dashboard, open the and select a user. You see their subscription topics, current status, and a log of when each preference changed and who changed it. Through the API, returns the change history for one user or the whole environment, and each change also arrives as a preferences:user:updated . To read the history or keep another system in sync, see . That history is per user. Your own team’s changes to the configuration land in the workspace instead, including a topic created or deleted, its channels changed, and its default status changed.

FAQ

Two bulk endpoints exist, both per user. replaces a user’s full override set atomically. updates topics additively. Each accepts up to 50 topics. No endpoint updates multiple users in one call, so a large import runs one bulk call per user.
A topic’s schedules are part of its digest object on topic create and replace, and a user’s choice is digest_schedule_id on their topic preference. You can also configure schedules in the , and users can pick theirs in the preference center. See .
The preferences:user:updated fires each time a user opts in, opts out, or changes a topic’s channels, on the hosted page, through an unsubscribe link, or through the API. To catch up on changes your endpoint missed, read .
You create topics in the or the Workspace Preferences API, and each carries a TypeID prefixed pt_. Reference that topic_id when mapping templates and when reading or writing user preferences.