Skip to main content
Give users Slack-style control over thread noise. A user can subscribe to a message thread to be notified of future replies, or unsubscribe from it to mute it. Users are automatically subscribed to a thread when they start it, reply in it, or are @-mentioned in it — and they can explicitly subscribe to any parent message, even one that has no replies yet. The SDK also exposes the list of threads a user participates in, so you can build a thread inbox. Let’s see how to work with thread subscriptions in CometChat’s iOS SDK.
Available from Chat SDK v4.1.9. These APIs require CometChatSDK v4.1.9 or later.Thread subscription builds on Threaded Messages. A thread is identified by the ID of its parent message — there is no separate thread ID.

Subscribe to a Thread

To subscribe to a thread, use the subscribeToThread method with the ID of the thread’s parent message. The call is idempotent — subscribing to a thread the user is already subscribed to succeeds silently. Subscribing to a message with zero replies is allowed; the user will be notified when the first reply arrives.

Unsubscribe from a Thread

To unsubscribe from a thread, use the unsubscribeFromThread method. This too is idempotent — unsubscribing from a thread the user is not subscribed to succeeds silently.
Unsubscribing is not sticky. If the user replies in the thread again, or is @-mentioned in it, they are automatically re-subscribed. Do not promise users “you won’t be notified about this thread again”.

Read the Subscription State

Every fetched message carries the logged-in user’s subscription state for its own thread on BaseMessage.threadSubscribed. It arrives with the message fetch, so rendering a subscribe control needs no extra network call and no separate state cache.
Because it is a plain Bool, read it only off a message you actually fetched:
A false on a message that was not fetched with the flag means “the server did not tell me”, not “the user is unsubscribed”. Only a fetched flag is authoritative. Render the unsubscribed state (an enabled “Subscribe” control) in the unknown case — never a spinner or a disabled control.
The property also has a setter, which is local only and performs no network call. It exists so you can align message objects you already hold with a truth you have just established — for example after a successful subscribeToThread:
To change the actual subscription, always use subscribeToThread / unsubscribeFromThread.

Fetch the Threads a User Participates In

To build a thread inbox — one row per thread the user is part of — create a ThreadsRequest using the ThreadsRequestBuilder. The list is the union of threads the user started, replied in, was mentioned in, or explicitly subscribed to. Every returned row is, by definition, a thread the user is subscribed to: participation is subscription, and unsubscribing removes the row.
Call fetchNext repeatedly to page forward; hasMore() tells you whether more pages exist. A ThreadsRequest is single-use and forward-only — there is no fetchPrevious. To refresh the list from the top, build a new request from the builder and replace your list with its results. Paging is internally keyed on a compound (updatedAt, id) cursor, matching the JS and Android SDKs. updatedAt alone is second-granular, so threads updated within the same second could not be separated by it and a page boundary could only step past the whole second — skipping every unseen row in it. Carrying the boundary row’s id makes the cursor address one exact row, so a block of same-second threads spills across pages instead of being dropped. You do not set this yourself; it matters only if you were previously working around duplicated or skipped rows.

The MessageThread Model

Each row is a MessageThread:
To order rows in your UI, sort on lastReply?.sentAt, falling back to parentMessage?.sentAt for zero-reply threads — not on updatedAt.
The list starts empty for every user when the feature launches — it fills up as users reply, get mentioned, and subscribe to threads. There is no historical backfill.

Keeping Your UI in Sync

The SDK does not expose a thread listener or thread-subscription callbacks. It deliberately does not cache subscription state: the state it could not fully observe drifted, so the server’s flag on the message is the single source of truth. Keep your UI in step yourself:
  • After your own subscribe/unsubscribe — update the message you hold by setting threadSubscribed in the success callback (local only, no network). This is the normal case and needs nothing else.
  • On the next fetchthreadSubscribed arrives with every fetched message, so a refresh always corrects the state.
  • For new replies — use the regular message listener (onTextMessageReceived and friends) and check parentMessageId to spot a threaded reply.
A subscribe or unsubscribe performed on the user’s other device produces no real-time event on this one. The state self-corrects on the next message fetch, so refresh when the app returns to the foreground.
Do not build a long-lived local cache of subscription state keyed by parent message id. That is exactly the design the SDK moved away from — it cannot observe every change, so it drifts. Read threadSubscribed off the message each time you render.

Notification Preferences

The notification preference for replies gains a new value so users can be notified only for threads they are subscribed to: SUBSCRIBE_TO_SUBSCRIBED_THREADS in the replies options.

Quoted replies are a separate preference

A quoted reply (a reply to one specific message) and a threaded reply (a message posted into a thread) are configured independently, through two different enums. They are not interchangeable — the raw value 4 means something different on each.
Both preferences exist on group and one-on-one preferences alike. See Notification Preferences for how to read and update a user’s preferences.

Error Handling