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.REQUIREDcannot 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. The API accepts it, and there is no console editor for it.
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.
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 is not 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 does not 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 , where 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 aDIGESTED 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 first option in the list is the default.
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:
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. The API returns current state only. gives you the live status and channel choices for every topic, but no change history. There is no “preference updated” webhook event, so poll the read endpoint to keep an external system in sync. 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.Limits & behavior
- Bulk is per user, up to 50 topics. Both bulk endpoints act on a single
user_id. There is no cross-user bulk endpoint. - Digest schedules have no write endpoint. Configure them in the editor. REST only triggers and lists digest instances.
- No preference-change webhook. History lives in the dashboard. The API exposes current state only.
REQUIREDis not a user choice, but it is a read value. Courier rejects a write that opts a user out of a required topic. Reads return"status": "REQUIRED"for those topics becausestatusfalls back to the topic default.- Reads return every topic. lists all topics in all sections, not just the ones the user has set.
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?
You configure digest schedules per topic in the . Over REST you can release a digest early and list its instances, but not create or change a schedule.
Is there a webhook for preference changes?
Is there a webhook for preference changes?
There is no preference-updated event. The dashboard shows change history, and returns current state, so poll it to sync an external system.
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.