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 cannot 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. The API accepts it, and there is no console editor for it.
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.

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 is not 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 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 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 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:
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. Schedules are read-only over REST. You set a schedule in the editor, not through a public endpoint. The API 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. 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.
  • REQUIRED is 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 because status falls back to the topic default.
  • Reads return every topic. lists all topics in all sections, not just the ones the user has set.

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.
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.
There is no preference-updated event. The dashboard shows change history, and returns current state, so poll it to sync an external system.
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.