Skip to main content
Someone comments on a document, and everyone watching it should hear about it. This guide models watchers as a list per entity. A list_id is a dotted namespace, so document.doc_a1b2.watchers names the watchers of one document. Subscribing a user creates that list, so there is nothing to set up first.

Prerequisites

  • A for each user you subscribe
  • A published to send

Name the list after the entity

Pick a naming convention before you write any code, because the name is the only thing tying a list to the thing it belongs to. A list_id holds up to six dot-separated segments:
Keeping the entity type in the first segment is what makes useful later. Entity ids with dots in them will not work, since a dot starts a new segment.

Wire up watching

1

Subscribe a user when they start watching

One call, and it is idempotent. creates the list if it does not exist, so you never check first.
Call this wherever your app already records a watch: an explicit Watch button, or implicitly when someone comments or is assigned.
2

Notify everyone watching

Address the list instead of a user. Courier fans out to every subscriber and applies each recipient’s own .
One send reaches every watcher, however many there are. You do not fan out yourself.
3

Unsubscribe when they stop watching

removes one watcher and leaves the rest alone.
When the entity itself is deleted, delete the whole list with rather than removing watchers one at a time.

Ask who is watching what

Both directions of the relationship are readable, which is what makes this usable as your app’s own watch state rather than a write-only copy of it. Both are .

Reach every entity of a kind

Because the entity type leads the list_id, one pattern can address many lists at once. Swap list_id for list_pattern, where * matches a single segment:
That reaches everyone watching any document, without you listing them. It is the reason the naming convention puts the type first and the relationship last.
A recipient watching several matched entities is deduplicated, so nobody receives the same message twice from one send.

Per-watcher preferences

A subscription can carry its own preferences, which apply to that subscription alone. Someone can follow a noisy project on digest while staying on instant notifications everywhere else. See for the status model.

Verify

1

Subscribe two users

Subscribe two test users to document.doc_a1b2.watchers.
2

Read the list back

Call and confirm both appear, which also proves the list was created by the subscribe call.
3

Send and check the logs

Send to the list, then open . You should see one message per subscriber, each with its own status.
4

Unsubscribe one and send again

The second send reaches one recipient. The unsubscribed user gets nothing.

FAQ

Subscribing creates the list when it does not exist, so a Watch button is one call with no setup and no existence check.
Courier applies each recipient’s preferences during fan-out, so an opted-out watcher is filtered and the rest still receive the message. The send does not fail.
Lists are built for fan-out, so a busy document with thousands of watchers is one send. Read members back with cursor pagination rather than all at once.
Add a MEMBER_OF filter on the recipient alongside context.tenant_id. Tenant context alone sets branding and preferences without restricting who receives the message. See .
Use an audience when membership follows from profile data, such as every user on the Pro plan. Watching is an explicit act that no profile field implies, so a list is the right shape.