Message threads

The message threads feature organizes conversations into threads, making it easier to follow and respond to specific topics.

Users need threads to:

  • Set topic-specific discussions - within group chats or 1:1 conversations, you can initiate or participate in separate threads dedicated to specific topics, ensuring focused and structured discussions.

  • Clarify and resolve issues - message threads let you address specific problems or questions within a conversation, allowing you to provide relevant input and ensure clarity and efficient problem-solving.

  • Reduce confusion - by allowing you to focus the conversation in a separate thread, you minimize cross-talk and make the conversation easier to follow.

In the Chat SDK, a thread is a separate channel created for a selected message. Such a thread channel gets the ID starting with PUBNUB_INTERNAL_THREAD followed by the channel ID and message ID (separated by underscores). The message for which you create a thread channel has the hasThread parameter set to true to distinguish it from other messages. All messages in a thread (thread messages) are ordered by timetokens containing info on when messages were published.

Each thread message (threadMessage) and thread channel (threadChannel) are separate entities in the Chat SDK that extend the message and channel entities. This means that they offer separate methods for handling threads but also let you use all methods under the message and channel entities (for example, for editing or deleting).

Interactive demo

Check how a sample implementation could look like in a React app showcasing how to reply to messages in threads and quote messages sent by others.

Want to implement something similar?

Read how to do that or go straight to the demo's source code.

Create thread

createThread() creates a thread (channel) for a selected message with additional options.

Method Signature

fun createThread(
text: String,
meta: Map<String, Any>? = null,
shouldStore: Boolean = true,
usePost: Boolean = false,
ttl: Int? = null,
quotedMessage: Message? = null,
files: List<InputFile>? = null,
usersToMention: Collection<String>? = null,
customPushData: Map<String, String>? = null
): PNFuture<ThreadChannel>

Input Parameters

* required
ParameterDescription
text *
Type: String
Default:
n/a
The text you want to send to the selected channel.
meta
Type: Map<String, Any>?
Default:
null
Additional details to publish with the request.
shouldStore
Type: Boolean
Default:
true
If true, the messages are stored in Message Persistence if enabled.
usePost
Type: Boolean
Default:
false
Use HTTP POST for the request.
ttl
Type: Int?
Default:
null
Time (in hours) the message should be stored.
quotedMessage
Type: Message?
Default:
null
Contains info about quoted messages.
files
Type: List<InputFile>?
Default:
null
List of files attached to the text message.
usersToMention
Type: Collection<String>?
Default:
null
Collection of user IDs to notify with a mention.
customPushData
Type: Map<String, String>?
Default:
null
Additional key-value pairs for push messages.

Output

TypeDescription
PNFuture<ThreadChannel>
Object returning the thread channel metadata for managing and interacting with the thread.

Basic Usage

Create a thread for the last message on the support channel.

channel.getHistory(count = 1).async { historyResult ->
historyResult.onSuccess { history ->
val messages = history.messages
if (messages.isNotEmpty()) {
val lastMessage = messages.last()
lastMessage.createThread(
text = "Thread starting text",
meta = mapOf("key" to "value"),
shouldStore = true,
ttl = 24
).async { threadResult ->
threadResult.onSuccess { threadChannel ->
// handle success
}.onFailure {
// handle failure
show all 24 lines

Send thread message

Reply to a message in a thread by calling the sendText() method from the previously created threadChannel object.

Method signature

Head over to the sendText() method section for details.

Basic usage

Send a message in a thread created for the last message on the support channel.

channel.getHistory(count = 1).async { historyResult ->
historyResult.onSuccess { history ->
val messages = history.messages
if (messages.isNotEmpty()) {
val lastMessage = messages.last()
lastMessage.createThread().async { threadResult ->
threadResult.onSuccess { threadChannel ->
threadChannel.sendText("Good job, guys!").async { /*...*/ }
}.onFailure {
// handle failure
}
}
} else {
// handle no messages found
}
show all 19 lines

Get thread

Get the thread channel on which the thread message is published.

Method signature

This method has the following signature:

message.getThread(): PNFuture<ThreadChannel>

Input

This method doesn't take any parameters.

Output

TypeDescription
PNFuture<ThreadChannel>
Object returning the thread channel metadata.

Basic usage

Get the thread channel created from the message with the 16200000000000001 timetoken.

val supportChannel = chat.getChannel("support")

supportChannel.getMessage(16200000000000001).async { messageResult ->
messageResult.onSuccess { message ->
message?.getThread()?.async { threadResult ->
threadResult.onSuccess { threadChannel ->
// handle success
}.onFailure {
// handle failure
}
} ?: run {
// handle message not found
}
}.onFailure {
// handle failure
show all 17 lines

Check if message starts thread

You can access the hasThread property of the Message object to check if a given message starts a thread.

Basic usage

Check if the message with the 16200000000000001 timetoken starts a thread.

// get the channel
pubnub.getChannel("support").async { result ->
result.onSuccess { channel ->
// fetch the message history
channel.getHistory(
startTimetoken = 16200000000000000,
endTimetoken = 16200000000000001,
count = 1
).async { historyResult ->
historyResult.onSuccess { history: PNHistoryResult ->
// reference the message
val message = history.messages.firstOrNull()

// check if the message starts a thread
if (message?.hasThread == true) {
show all 29 lines

Get thread message updates

You can receive updates when specific message threads and related message reactions are added, edited, or removed on other clients using the streamUpdatesOn() on the ThreadMessage object.

This method accepts a callback function as an argument. The Chat SDK invokes this callback whenever someone adds, edits or deletes a message, or adds or removes a message reaction to/from a list of thread messages.

Underneath, this method subscribes the current user to a channel and adds a message reactions event listener to receive all messageAction events of type added or removed. These methods also return the unsubscribe function you can invoke to stop receiving messageAction events and unsubscribe from the channel.

Method signature

This method takes the following parameters:

class ThreadMessage {
companion object {
fun streamUpdatesOn(
messages: Collection<ThreadMessage>,
callback: (messages: Collection<ThreadMessage>) -> Unit
): AutoCloseable
}
}

Input

* required
ParameterDescription
messages *
Type: Collection<ThreadMessage>
Default:
n/a
Collection of ThreadMessage objects for which you want to get updates on changed message threads or related message reactions.
callback *
Type: (messages: Collection<ThreadMessage>) -> Unit
Default:
n/a
Callback function passed to the method as a parameter. It defines the custom behavior to be executed when detecting changes in message threads or related message reactions.

Output

TypeDescription
AutoCloseable
Interface that lets you stop receiving message thread-related updates by invoking the close() method.

Basic usage

Get message threads and message reaction-related updates for the first page of messages published in a thread.

val messageWithThread: Message

messageWithThread.getThread().async {
it.onSuccess { threadChannel ->
threadChannel.getHistory().async {
it.onSuccess { historyResponse ->
// stream updates for the fetched thread messages
val autoCloseable =
ThreadMessage.streamUpdatesOn(messages = historyResponse.messages) { updatedThreadMessages ->
updatedThreadMessages.forEach { updatedThreadMessage ->
println("-=Updated thread message: $updatedThreadMessage")
}
}

// to stop streaming updates at some later point, use:
show all 24 lines

Other examples

Stop listening to updates for the last ten messages published in a thread.

val messageWithThread: Message

messageWithThread.getThread().async {
it.onSuccess { threadChannel ->
threadChannel.getHistory(count = 10).async {
it.onSuccess { historyResponse ->
// stream updates for the fetched thread messages
val autoCloseable =
ThreadMessage.streamUpdatesOn(messages = historyResponse.messages) { updatedThreadMessages ->
updatedThreadMessages.forEach { updatedThreadMessage ->
println("-=Updated thread message: $updatedThreadMessage")
}
}

// stop listening to updates at some later point
show all 26 lines

Get historical thread message

getHistory() called on the threadChannel object fetches historical thread messages from that thread channel.

Method signature

This method takes the following parameters:

threadChannel.getHistory(
startTimetoken: Long?,
endTimetoken: Long?,
count: Int
): PNFuture<HistoryResponse<ThreadMessage>>

Input

* required
ParameterDescription
startTimetoken
Type: Long
Default:
n/a
Timetoken delimiting the start of a time slice (exclusive) to pull thread messages from. For details, refer to Message Persistence.
endTimetoken
Type: Long
Default:
n/a
Timetoken delimiting the end of a time slice (inclusive) to pull thread messages from. For details, refer to the Message Persistence.
count *
Type: Int
Default:
25
Number of historical thread messages to return for the channel in a single call. Since each call returns all attached message reactions by default, the maximum number of returned thread messages is 25. For more details, refer to the description of the includeMessageActions parameter in the Kotlin SDK docs.

Output

ParameterDescription
PNFuture<HistoryResponse<ThreadMessage>>
Type: object
PNFuture holding HistoryResponse containing a list of ThreadMessage objects and a Boolean flag indicating if there are more messages available.

By default, each call returns all message reactions and metadata attached to the retrieved thread messages.

Basic usage

From the thread created for the last message in the support channel, fetch 10 historical thread messages that are older than the timetoken 15343325214676133.

// reference the "support" channel asynchronously
chat.getChannel("support").async { channelResult ->
channelResult.onSuccess { channel ->
if (channel != null) {
// get the last message on the channel, which is the root message for the thread
channel.getHistory(count = 1).async { messageResult ->
messageResult.onSuccess { historyResponse ->
val message = historyResponse.messages.firstOrNull()

if (message != null) {
// get the thread channel
message.getThread().async { threadChannelResult ->
threadChannelResult.onSuccess { threadChannel ->
if (threadChannel != null) {
// fetch the required historical messages
show all 55 lines

Remove thread

removeThread() removes a thread (channel) for a selected message.

Method signature

This method has the following signature:

message.removeThread(): PNFuture<Pair<PNRemoveMessageActionResult, Channel>>

Input

This method doesn't take any parameters.

Output

TypeDescription
PNFuture<Pair<PNRemoveMessageActionResult, Channel>>
A pair of values containing an object with details about the result of the remove message reaction (indicating whether the message was successfully removed and potentially including additional metadata or information about the removal) aand the updated channel object after the removal of the thread.

Basic usage

Remove a thread for the last message on the support channel.

// retrieve the "support" channel
chat.getChannel("support").async { channelResult ->
channelResult.onSuccess { channel ->
// get the last message from the channel’s history
channel.getHistory(count = 1).async { historyResult ->
historyResult.onSuccess { historyResponse ->
val message = historyResponse.messages.firstOrNull()
if (message != null) {
// remove the thread for the last message
message.removeThread().async { removeThreadResult ->
removeThreadResult.onSuccess { (removeMessageResult, updatedChannel) ->
println("Thread removed successfully.")
println("Result: $removeMessageResult")
println("Updated Channel: $updatedChannel")
}.onFailure { throwable ->
show all 32 lines

Pin thread message to thread channel

pinMessage() called on the ThreadChannel object pins a selected thread message to the thread channel.

Method signature

This method takes the following parameters:

threadChannel.pinMessage(
message: Message
): PNFuture<ThreadChannel>

Input

* required
ParameterDescription
message *
Type: Message
Default:
n/a
Message object you want to pin to the selected thread channel.

Output

TypeDescription
PNFuture<ThreadChannel>
Object returning the thread channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was pinned

pinnedMessageChannelID to mark the channel on which the message was pinned to the thread channel (unpinning was performed either directly on the parent channel or on a thread channel).

Basic usage

A thread was created for the last message in the support parent channel. Pin the last message from this thread to the thread channel.

// reference the "support" channel where the root message for the thread is published
chat.getChannel("support").async { channelResult ->
channelResult.onSuccess { channel ->
// get the last message on the channel, which is the root message for the thread
channel.getHistory(null, null, 1).async { historyResult ->
historyResult.onSuccess { history ->
val message = history.messages[0]

// get the thread channel
message.getThread().async { threadChannelResult ->
threadChannelResult.onSuccess { threadChannel ->
// get the last message on the thread channel
threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
threadHistoryResult.onSuccess { threadHistory ->
val threadMessage = threadHistory.messages[0]
show all 40 lines

Pin thread message to parent channel

You can pin a selected thread message to the parent channel with pinMessageToParentChannel() and pinToParentChannel(). They give the same output, and the only difference is that you call a given method either on the ThreadChannel or the ThreadMessage object. Depending on the object, these methods take different input parameters - you have to specify the thread message you want to pin or not because it's already known.

Method signature

These methods take the following parameters:

  • pinMessageToParentChannel()

    threadChannel.pinMessageToParentChannel(
    message: ThreadMessage
    ): PNFuture<Channel>
  • pinToParentChannel()

    threadMessage.pinToParentChannel(): PNFuture<Channel>

Input

ParameterRequired in pinMessageToParentChannel()Required in pinToParentChannel()Description
message
Type: ThreadMessage
Default:
n/a
Yes
No
ThreadMessage object you want to pin to the selected parent channel.

Output

TypeDescription
PNFuture<Channel>
Object returning the channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was pinned

pinnedMessageChannelID to mark the channel on which the message was pinned to the parent channel (pinning was performed either directly on the parent channel or on a thread channel).

Basic usage

A thread was created for the last message in the support parent channel. Pin the last message from this thread to the parent channel.

  • pinMessageToParentChannel()

    // reference the "support" channel where the root message for the thread is published
    chat.getChannel("support").async { channelResult ->
    channelResult.onSuccess { channel ->
    // get the last message on the channel, which is the root message for the thread
    channel.getHistory(null, null, 1).async { historyResult ->
    historyResult.onSuccess { history ->
    val message = history.messages[0]

    // get the thread channel
    message.getThread().async { threadChannelResult ->
    threadChannelResult.onSuccess { threadChannel ->
    // get the last message on the thread channel
    threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
    threadHistoryResult.onSuccess { threadHistory ->
    val threadMessage = threadHistory.messages[0]
    show all 40 lines
  • pinToParentChannel()

    // reference the "support" channel where the root message for the thread is published
    chat.getChannel("support").async { channelResult ->
    channelResult.onSuccess { channel ->
    // get the last message on the channel, which is the root message for the thread
    channel.getHistory(null, null, 1).async { historyResult ->
    historyResult.onSuccess { history ->
    val message = history.messages[0]

    // get the thread channel
    message.getThread().async { threadChannelResult ->
    threadChannelResult.onSuccess { threadChannel ->
    // get the last message on the thread channel
    threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
    threadHistoryResult.onSuccess { threadHistory ->
    val threadMessage = threadHistory.messages[0]
    show all 40 lines

Unpin thread message from thread channel

unpinMessage() called on the ThreadChannel object unpins the previously pinned thread message from the thread channel.

Method signature

This method has the following signature:

threadChannel.unpinMessage(): PNFuture<ThreadChannel>

Input

This method doesn't take any parameters.

Output

TypeDescription
PNFuture<ThreadChannel>
Object returning the thread channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was unpinned

pinnedMessageChannelID to mark the channel on which the message was unpinned from the thread channel (unpinning was performed either directly on the parent channel or on a thread channel).

Basic usage

Unpin the thread message from the thread (channel) created for the last message on the support channel.

// reference the "support" channel where the root message for the thread is published
chat.getChannel("support").async { channelResult ->
channelResult.onSuccess { channel ->
// get the last message on the channel, which is the root message for the thread
channel.getHistory(null, null, 1).async { historyResult ->
historyResult.onSuccess { history ->
val message = history.messages[0]

// get the thread channel
message.getThread().async { threadChannelResult ->
threadChannelResult.onSuccess { threadChannel ->
// get the last message on the thread channel
threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
threadHistoryResult.onSuccess { threadHistory ->
val threadMessage = threadHistory.messages[0]
show all 40 lines

Unpin thread message from parent channel

You can unpin the previously pinned thread message from the parent channel with unpinMessageFromParentChannel() and unpinFromParentChannel().

Method signature

These methods have the following signatures:

  • unpinMessageFromParentChannel()

    threadChannel.unpinMessageFromParentChannel(): PNFuture<Channel>
  • unpinFromParentChannel()

    threadMessage.unpinFromParentChannel(): PNFuture<Channel>

Input

These methods don't take any parameters.

Output

TypeDescription
PNFuture<Channel>
Object returning the channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was unpinned

pinnedMessageChannelID to mark the channel on which the message was unpinned from the parent channel (unpinning was performed either directly on the parent channel or on a thread channel).

Basic usage

Unpin the thread message from the support parent channel.

  • unpinMessageFromParentChannel()

    // reference the "support" channel where the root message for the thread is published
    chat.getChannel("support").async { channelResult ->
    channelResult.onSuccess { channel ->
    // get the last message on the channel, which is the root message for the thread
    channel.getHistory(null, null, 1).async { historyResult ->
    historyResult.onSuccess { history ->
    val message = history.messages[0]

    // get the thread channel
    message.getThread().async { threadChannelResult ->
    threadChannelResult.onSuccess { threadChannel ->
    // get the last message on the thread channel
    threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
    threadHistoryResult.onSuccess { threadHistory ->
    val threadMessage = threadHistory.messages[0]
    show all 40 lines
  • unpinFromParentChannel()

    // reference the "support" channel where the root message for the thread is published
    chat.getChannel("support").async { channelResult ->
    channelResult.onSuccess { channel ->
    // get the last message on the channel, which is the root message for the thread
    channel.getHistory(null, null, 1).async { historyResult ->
    historyResult.onSuccess { history ->
    val message = history.messages[0]

    // get the thread channel
    message.getThread().async { threadChannelResult ->
    threadChannelResult.onSuccess { threadChannel ->
    // get the last message on the thread channel
    threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->
    threadHistoryResult.onSuccess { threadHistory ->
    val threadMessage = threadHistory.messages[0]
    show all 40 lines

Create thread message draft

createThreadMessageDraft() creates a message draft for composing a message in an existing thread channel. This allows users to craft a message with additional options like user suggestion and typing indicator.

Method Signature

fun Message.createThreadMessageDraft(
userSuggestionSource: UserSuggestionSource = UserSuggestionSource.CHANNEL,
isTypingIndicatorTriggered: Boolean = true,
userLimit: Int = 10,
channelLimit: Int = 10
): PNFuture<MessageDraft>

Input Parameters

* required
ParameterDescription
userSuggestionSource
Type: UserSuggestionSource
Default:
UserSuggestionSource.CHANNEL
Scope for searching suggested users.
isTypingIndicatorTriggered
Type: Boolean
Default:
true
Whether modifying the message text triggers a typing indicator.
userLimit
Type: Int
Default:
10
Limit on number of users returned for mentions.
channelLimit
Type: Int
Default:
10
Limit on number of channels returned for references.

Output

TypeDescription
PNFuture<MessageDraft>
Object used for composing and formatting a message draft in a thread channel.

Basic Usage

Create a message draft for the last message in the support channel and set up user suggestions and typing indicator.

channel.getHistory(count = 1).async { historyResult ->
historyResult.onSuccess { history ->
val messages = history.messages
if (messages.isNotEmpty()) {
val lastMessage = messages.last()
lastMessage.createThreadMessageDraft(
userSuggestionSource = UserSuggestionSource.CHANNEL,
isTypingIndicatorTriggered = true,
userLimit = 10,
channelLimit = 10
).async { draftResult ->
draftResult.onSuccess { messageDraft ->
// Handle success: Compose your message using messageDraft
}.onFailure {
// Handle failure
show all 24 lines

Create thread (deprecated)

Deprecated

This method is deprecated. Use Create thread instead.

createThread() creates a thread (channel) for a selected message.

Method signature

This method has the following signature:

message.createThread(): PNFuture<ThreadChannel>
Input

This method doesn't take any parameters.

Output
TypeDescription
PNFuture<ThreadChannel>
Object returning the thread channel metadata for the message updated with the hasThread parameter (and a threadRootId action type underneath).

Basic usage

Create a thread for the last message on the support channel.

channel.getHistory(count = 1).async { historyResult ->
historyResult.onSuccess { history ->
val messages = history.messages
if (messages.isNotEmpty()) {
val lastMessage = messages.last()
lastMessage.createThread().async { threadResult ->
threadResult.onSuccess { threadChannel ->
// handle success
}.onFailure {
// handle failure
}
}
} else {
// handle no messages found
}
show all 19 lines
Last updated on