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 examplept_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, orREQUIRED. It seeds every user’s status until they override it.REQUIREDcan’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 incustom_routingand applies them only whenhas_custom_routingis 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.
status, eitherOPTED_INorOPTED_OUT. These are the only two values a user can set.custom_routing, with itshas_custom_routingflag.topic_id, the topic the override applies to.
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’sdefault_status plus an effective status. The effective status is the user’s override when one exists, and the topic default otherwise:
statusis effective, not stored. A topic the user never touched still returns astatus, copied fromdefault_status. AREQUIREDtopic therefore reads back as"status": "REQUIRED". Branch ondefault_statusto know whether the user chose anything.- Paging isn’t implemented.
moreis alwaysfalseand no cursor is returned, so a single call gives you every topic.
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’sdigest 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:
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 apreferences: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
Is there a bulk preferences endpoint?
Is there a bulk preferences endpoint?
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.
Can I set a digest schedule through the API?
Can I set a digest schedule through the API?
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 .Is there a webhook for preference changes?
Is there a webhook for preference changes?
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 .Where do topic IDs come from?
Where do topic IDs come from?
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.