---
source_url: https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/threads
title: Message threads
updated_at: 2026-06-15T12:11:35.132Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message threads

Organize conversations into threads for topic-specific discussions.

Benefits:

* Focus discussions on specific topics
* Clarify and resolve issues without cross-talk
* Keep main channel conversations clean

Thread channels have IDs starting with `PUBNUB_INTERNAL_THREAD_{channel_id}_{message_id}`. Messages with threads have `hasThread: true`.

[ThreadMessage](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/thread-message) and [ThreadChannel](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/thread-channel) extend the base `message` and `channel` entities with thread-specific methods.

## Interactive demo

Check what a sample implementation could look like in a React app showcasing how to reply to messages in threads and [quote messages](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/quotes) sent by others.

:::tip Want to implement something similar?
Read [how to](https://www.pubnub.com/how-to/chat-sdk-create-threads-and-quote-messges/) do that or go straight to the demo's [source code](https://github.com/PubNubDevelopers/Chat-SDK-How-Tos/tree/main/threads-quotes).
:::

## Create thread

`createThread()` creates a thread (channel) for a selected message, sends the first reply, and returns both the thread channel and the updated parent message with `hasThread: true`.

### Method signature

```kotlin
message.createThread(
    text: String,
    params: SendTextParams = SendTextParams(),
): PNFuture<CreateThreadResult>
```

### Input parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| text | String | Yes |  | The text you want to send to the selected channel. |
| params | SendTextParams | Optional | `SendTextParams()` | Object with additional send options. |
| params.meta | Map<String, | Optional | `null` | Additional details to publish with the request. |
| params.shouldStore | Boolean | Optional | `true` | If true, the messages are stored in Message Persistence if enabled. |
| params.usePost | Boolean | Optional | `false` | Use HTTP POST for the request. |
| params.ttl | Int? | Optional | `null` | Time (in hours) the message should be stored. |
| params.usersToMention | Collection<String>? | Optional | `null` | Collection of user IDs to notify with a mention. |
| params.customPushData | Map<String, | Optional | `null` | Additional key-value pairs for push messages. |

### Output

| Type | Description |
| --- | --- |
| `PNFuture<CreateThreadResult>` | Object containing both the thread channel and the updated parent message. |

The returned `CreateThreadResult` object contains:

| Property | Description |
| --- | --- |
| `threadChannel`Type: `ThreadChannel` | The newly created thread channel for sending and receiving messages. |
| `parentMessage`Type: `Message` | The updated parent message with `hasThread` set to `true`. |

### Sample code

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

```kotlin
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",
                params = SendTextParams(
                    meta = mapOf("key" to "value"),
                    shouldStore = true,
                    ttl = 24
                )
            ).async { threadResult ->
                threadResult.onSuccess { createThreadResult ->
                    val threadChannel = createThreadResult.threadChannel
                    val parentMessage = createThreadResult.parentMessage
                    // parentMessage now has hasThread = true without needing to re-fetch
                    println("Thread created: ${threadChannel.id}")
                    println("Parent has thread: ${parentMessage.hasThread}") // true
                }.onFailure {
                    // handle failure
                }
            }
        } else {
            // handle no messages found
        }
    }.onFailure {
        // handle failure
    }
}
```

## Create thread with result

:::warning Deprecated
This method is deprecated. Use [createThread()](#create-thread) instead, which now returns `CreateThreadResult` directly.
:::

`createThreadWithResult()` creates a thread, sends the first reply, and returns both the thread channel and updated parent message with `hasThread: true`. Use this when you need immediate confirmation without a separate fetch.

### Method signature

```kotlin
message.createThreadWithResult(
    text: String,
    params: SendTextParams = SendTextParams(),
): PNFuture<CreateThreadResult>
```

### Input parameters

| Parameter | Description |
| --- | --- |
| `text` *Type: `String`Default: n/a | Text that you want to send as the first message in the thread. |
| `params`Type: `SendTextParams`Default: `SendTextParams()` | Object with additional send options. |
| `params.meta`Type: `Map<String, Any>?`Default: `null` | Additional details to publish with the request. |
| `params.shouldStore`Type: `Boolean`Default: `true` | If `true`, the messages are stored in Message Persistence if enabled. |
| `params.usePost`Type: `Boolean`Default: `false` | Use HTTP POST for the request. |
| `params.ttl`Type: `Int?`Default: `null` | Time (in hours) the message should be stored in Message Persistence. If `shouldStore` is `true` and `ttl` is `0`, the message is stored with no expiry time. If `shouldStore` is `true` and `ttl` is set to a value, the message is stored with that expiry time. If `shouldStore` is `false`, this parameter is ignored. If not specified, the expiration defaults to the expiry value for the keyset. |
| `params.usersToMention`Type: `Collection<String>?`Default: `null` | Collection of user IDs to automatically notify with a mention after this message is sent. |
| `params.customPushData`Type: `Map<String, String>?`Default: `null` | Additional key-value pairs that will be added to the FCM and/or APNS push messages. |

### Output

| Type | Description |
| --- | --- |
| `PNFuture<CreateThreadResult>` | Object containing both the `threadChannel` and the updated `parentMessage`. |

The returned `CreateThreadResult` object contains:

| Property | Description |
| --- | --- |
| `threadChannel`Type: `ThreadChannel` | The newly created thread channel for sending and receiving messages. |
| `parentMessage`Type: `Message` | The updated parent message with `hasThread` set to `true`. |

### Sample code

Create a thread for the last message on the `support` channel and get the updated parent message.

```kotlin
channel.getHistory(count = 1).async { historyResult ->
    historyResult.onSuccess { history ->
        val messages = history.messages
        if (messages.isNotEmpty()) {
            val lastMessage = messages.last()
            lastMessage.createThreadWithResult(
                text = "This is the first reply in the thread",
                params = SendTextParams()
            ).async { result ->
                result.onSuccess { createThreadResult ->
                    val threadChannel = createThreadResult.threadChannel
                    val parentMessage = createThreadResult.parentMessage

                    // parentMessage now has hasThread = true without needing to re-fetch
                    println("Thread created: ${threadChannel.id}")
                    println("Parent has thread: ${parentMessage.hasThread}") // true
                }.onFailure {
                    // handle failure
                }
            }
        } else {
            // handle no messages found
        }
    }.onFailure {
        // handle failure
    }
}
```

## Send thread message

Reply to a message in a thread by calling `sendText()` on the `threadChannel` from the `CreateThreadResult` returned by `createThread()`.

### Method signature

Head over to the [sendText() method](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/send-receive) section for details.

### Sample code

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

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

## Get thread

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

### Method signature

This method has the following signature:

```kotlin
message.getThread(): PNFuture<ThreadChannel>
```

#### Input

This method doesn't take any parameters.

#### Output

| Type | Description |
| --- | --- |
| `PNFuture<ThreadChannel>` | Object returning the thread channel metadata. |

### Sample code

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

```kotlin
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
    }
}
```

## Check if message starts thread

`hasThread` indicates if a message starts a thread.

### Sample code

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

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

                // check if the message starts a thread
                if (message?.hasThread == true) {
                    println("The message starts a thread.")
                } else {
                    println("The message does not start a thread.")
                }
            }.onFailure { error ->
                // handle failure
                println("Failed to fetch history: ${error.message}")
            }
        }
    }.onFailure { error ->
        // handle failure
        println("Failed to get channel: ${error.message}")
    }
}
```

## Get thread updates

`onThreadMessageUpdated()` on `ThreadMessage` receives updates when thread messages or [reactions](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/reactions) change.

The callback fires whenever messages are added, edited, deleted, or reactions change. Returns an `AutoCloseable`.

### Method signature

```kotlin
threadMessage.onThreadMessageUpdated(callback: (message: ThreadMessage) -> Unit): AutoCloseable
```

#### Input

| Parameter | Description |
| --- | --- |
| `callback` *Type: `(message: ThreadMessage) -> Unit`Default: n/a | Callback function executed when the thread message or its reactions change. |

#### Output

| Type | Description |
| --- | --- |
| `AutoCloseable` | Interface that lets you stop receiving thread message updates by invoking the `close()` method. |

### Sample code

Get updates for a thread message published in a thread.

```kotlin
val messageWithThread: Message

messageWithThread.getThread().async {
    it.onSuccess { threadChannel ->
        threadChannel.getHistory().async {
            it.onSuccess { historyResponse ->
                val firstThreadMessage = historyResponse.messages.firstOrNull()
                firstThreadMessage?.let { threadMsg ->
                    val subscription = threadMsg.onThreadMessageUpdated { updatedMessage ->
                        println("Updated thread message: $updatedMessage")
                    }
                    // subscription.close() to stop
                }
            }.onFailure { /* ... */ }
        }
    }.onFailure { /* ... */ }
}
```

## Get thread updates

`streamUpdatesOn()` on `ThreadMessage` receives updates when thread messages or [reactions](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/reactions) change.

:::note Stream update behavior
`streamUpdatesOn()` returns the complete list of monitored thread messages on each change.
:::

### Method signature

This method takes the following parameters:

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

#### Input

| Parameter | Description |
| --- | --- |
| `messages` *Type: `Collection<ThreadMessage>`Default: n/a | Collection of [ThreadMessage objects](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/thread-message) 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

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

### Sample code

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

```kotlin
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 ->
                        // The callback receives the complete list of all thread messages you're monitoring
                        // (including all reactions) each time any change occurs.
                        updatedThreadMessages.forEach { updatedThreadMessage ->
                            println("-=Updated thread message: $updatedThreadMessage")
                        }
                    }

                // to stop streaming updates at some later point, use:
                // autoCloseable.close()
            }.onFailure {
                // ...
            }
        }
    }.onFailure {

    }
}
```

### Other examples

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

```kotlin
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 ->
                        // The callback receives the complete list of all thread messages you're monitoring
                        // (including all reactions) each time any change occurs.
                        updatedThreadMessages.forEach { updatedThreadMessage ->
                            println("-=Updated thread message: $updatedThreadMessage")
                        }
                    }

                // stop listening to updates at some later point
                // Note: You will add this at the point where you want to stop listening
                // In this example, it is immediate for demonstration purposes.
                autoCloseable.close() // Stop listening to updates
            }.onFailure {
                // ...
            }
        }
    }.onFailure {
        // ...
    }
}
```

## Get historical thread message

`getHistory()` on `threadChannel` fetches historical thread messages.

### Method signature

This method takes the following parameters:

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

#### Input

| Parameter | Description |
| --- | --- |
| `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](https://www.pubnub.com/docs/sdks/kotlin/api-reference/storage-and-playback). |
| `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](https://www.pubnub.com/docs/sdks/kotlin/api-reference/storage-and-playback). |
| `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](https://www.pubnub.com/docs/sdks/kotlin/api-reference/storage-and-playback#methods). |

#### Output

| Parameter | Description |
| --- | --- |
| `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.

### Sample code

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

```kotlin
// 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
                                    threadChannel.getHistory(
                                        startTimetoken = null,
                                        endTimetoken = 15343325214676133,
                                        count = 10
                                    ).async { threadMessageResult ->
                                        threadMessageResult.onSuccess { threadHistoryResponse ->
                                            // handle the retrieved thread messages (success)
                                            val threadMessages = threadHistoryResponse.messages
                                            threadMessages.forEach { println(it) }
                                        }.onFailure {
                                            // handle failure in fetching thread messages
                                            println("Failed to fetch thread messages")
                                            it.printStackTrace()
                                        }
                                    }
                                } else {
                                    println("Failed to retrieve the thread channel")
                                }
                            }.onFailure {
                                println("Failed to get the thread channel")
                                it.printStackTrace()
                            }
                        }
                    } else {
                        println("No messages found in the support channel")
                    }
                }.onFailure {
                    println("Failed to get history for the support channel")
                    it.printStackTrace()
                }
            }
        } else {
            println("Support channel does not exist")
        }
    }.onFailure {
        // handle failure in getting the support channel
        println("Failed to retrieve the support channel")
        it.printStackTrace()
    }
}
```

## Remove thread

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

### Method signature

This method has the following signature:

```kotlin
message.removeThread(): PNFuture<Unit>
```

#### Input

This method doesn’t take any parameters.

#### Output

| Type | Description |
| --- | --- |
| `PNFuture<Unit>` | Returns when the thread is successfully removed. |

### Sample code

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

```kotlin
// 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 {
                            println("Thread removed successfully.")
                        }.onFailure { throwable ->
                            println("Failed to remove thread.")
                            throwable.printStackTrace()
                        }
                    }
                } else {
                    println("No messages found in the support channel.")
                }
            }.onFailure { throwable ->
                println("Failed to get history for the support channel.")
                throwable.printStackTrace()
            }
        }
    }.onFailure { throwable ->
        println("Failed to retrieve the support channel.")
        throwable.printStackTrace()
    }
}
```

## Pin thread message to thread channel

`pinMessage()` on `ThreadChannel` pins a thread message to the thread channel.

### Method signature

This method takes the following parameters:

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

#### Input

| Parameter | Description |
| --- | --- |
| `message` *Type: `Message`Default: n/a | [Message object](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/message) you want to pin to the selected thread channel. |

#### Output

| Type | Description |
| --- | --- |
| `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). |

### Sample code

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

```kotlin
// 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]

                                // pin the last message from the thread to the thread channel
                                threadChannel.pinMessage(threadMessage).async { pinResult ->
                                    pinResult.onSuccess { pinnedChannel ->
                                        // handle success (pinnedChannel refers to the thread channel with the pinned message)
                                    }.onFailure { pinError ->
                                        // Handle failure
                                    }
                                }
                            }.onFailure { threadHistoryError ->
                                // handle failure
                            }
                        }
                    }.onFailure { threadChannelError ->
                        // handle failure
                    }
                }
            }.onFailure { historyError ->
                // handle failure
            }
        }
    }.onFailure { channelError ->
        // handle failure
    }
}
```

## Pin thread message to parent channel

`pinMessageToParentChannel()` (on `ThreadChannel`) and `pinToParentChannel()` (on `ThreadMessage`) pin a thread message to the parent channel.

### Method signature

These methods take the following parameters:

* pinMessageToParentChannel() 1threadChannel.pinMessageToParentChannel(2 message: ThreadMessage3): PNFuture<Channel>
* pinToParentChannel() 1threadMessage.pinToParentChannel(): PNFuture<Channel>

#### Input

| Parameter | Required in `pinMessageToParentChannel()` | Required in `pinToParentChannel()` | Description |
| --- | --- | --- | --- |
| `message`Type: `ThreadMessage`Default: n/a | Yes | No | [ThreadMessage object](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/thread-message) you want to pin to the selected parent channel. |

#### Output

| Type | Description |
| --- | --- |
| `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). |

### Sample code

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() 1// reference the "support" channel where the root message for the thread is published2chat.getChannel("support").async { channelResult ->3 channelResult.onSuccess { channel ->4 // get the last message on the channel, which is the root message for the thread5 channel.getHistory(null, null, 1).async { historyResult ->6 historyResult.onSuccess { history ->7 val message = history.messages[0]8 9 // get the thread channel10 message.getThread().async { threadChannelResult ->11 threadChannelResult.onSuccess { threadChannel ->12 // get the last message on the thread channel13 threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->14 threadHistoryResult.onSuccess { threadHistory ->15 val threadMessage = threadHistory.messages[0]16 17 // pin the message to the parent channel18 threadChannel.pinMessageToParentChannel(threadMessage).async { pinResult ->19 pinResult.onSuccess { pinnedChannel ->20 // handle success (pinnedChannel refers to the parent channel with the pinned message)21 }.onFailure { pinError ->22 // handle failure23 }24 }25 }.onFailure { threadHistoryError ->26 // handle failure27 }28 }29 }.onFailure { threadChannelError ->30 // handle failure31 }32 }33 }.onFailure { historyError ->34 // handle failure35 }36 }37 }.onFailure { channelError ->38 // handle failure39 }40}
* pinToParentChannel() 1// reference the "support" channel where the root message for the thread is published2chat.getChannel("support").async { channelResult ->3 channelResult.onSuccess { channel ->4 // get the last message on the channel, which is the root message for the thread5 channel.getHistory(null, null, 1).async { historyResult ->6 historyResult.onSuccess { history ->7 val message = history.messages[0]8 9 // get the thread channel10 message.getThread().async { threadChannelResult ->11 threadChannelResult.onSuccess { threadChannel ->12 // get the last message on the thread channel13 threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->14 threadHistoryResult.onSuccess { threadHistory ->15 val threadMessage = threadHistory.messages[0]16 17 // pin the message to the parent channel using threadMessage.pinToParentChannel()18 threadMessage.pinToParentChannel().async { pinResult ->19 pinResult.onSuccess { pinnedChannel ->20 // handle success (pinnedChannel refers to the parent channel with the pinned message)21 }.onFailure { pinError ->22 // handle failure23 }24 }25 }.onFailure { threadHistoryError ->26 // handle failure27 }28 }29 }.onFailure { threadChannelError ->30 // handle failure31 }32 }33 }.onFailure { historyError ->34 // handle failure35 }36 }37 }.onFailure { channelError ->38 // handle failure39 }40}

## Unpin thread message from thread channel

`unpinMessage()` on `ThreadChannel` unpins the pinned thread message.

### Method signature

This method has the following signature:

```kotlin
threadChannel.unpinMessage(): PNFuture<ThreadChannel>
```

#### Input

This method doesn't take any parameters.

#### Output

| Type | Description |
| --- | --- |
| `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). |

### Sample code

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

```kotlin
// 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]

                                // unpin the last message from the thread channel
                                threadChannel.unpinMessage().async { unpinResult ->
                                    unpinResult.onSuccess { unpinnedThreadChannel ->
                                        // handle success (unpinnedThreadChannel refers to the thread channel without the unpinned message)
                                    }.onFailure { unpinError ->
                                        // Handle failure
                                    }
                                }
                            }.onFailure { threadHistoryError ->
                                // handle failure
                            }
                        }
                    }.onFailure { threadChannelError ->
                        // handle failure
                    }
                }
            }.onFailure { historyError ->
                // handle failure
            }
        }
    }.onFailure { channelError ->
        // handle failure
    }
}
```

## Unpin thread message from parent channel

`unpinMessageFromParentChannel()` (on `ThreadChannel`) and `unpinFromParentChannel()` (on `ThreadMessage`) unpin a thread message from the parent channel.

### Method signature

These methods have the following signatures:

* unpinMessageFromParentChannel() 1threadChannel.unpinMessageFromParentChannel(): PNFuture<Channel>
* unpinFromParentChannel() 1threadMessage.unpinFromParentChannel(): PNFuture<Channel>

#### Input

These methods don't take any parameters.

#### Output

| Type | Description |
| --- | --- |
| `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). |

### Sample code

Unpin the thread message from the `support` parent channel.

* unpinMessageFromParentChannel() 1// reference the "support" channel where the root message for the thread is published2chat.getChannel("support").async { channelResult ->3 channelResult.onSuccess { channel ->4 // get the last message on the channel, which is the root message for the thread5 channel.getHistory(null, null, 1).async { historyResult ->6 historyResult.onSuccess { history ->7 val message = history.messages[0]8 9 // get the thread channel10 message.getThread().async { threadChannelResult ->11 threadChannelResult.onSuccess { threadChannel ->12 // get the last message on the thread channel13 threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->14 threadHistoryResult.onSuccess { threadHistory ->15 val threadMessage = threadHistory.messages[0]16 17 // unpin the last message from the parent "support" channel18 threadChannel.unpinMessageFromParentChannel().async { unpinResult ->19 unpinResult.onSuccess { parentChannel ->20 // handle success (parentChannel refers to the parent "support" channel without the unpinned message)21 }.onFailure { unpinError ->22 // Handle failure23 }24 }25 }.onFailure { threadHistoryError ->26 // handle failure27 }28 }29 }.onFailure { threadChannelError ->30 // handle failure31 }32 }33 }.onFailure { historyError ->34 // handle failure35 }36 }37 }.onFailure { channelError ->38 // handle failure39 }40}
* unpinFromParentChannel() 1// reference the "support" channel where the root message for the thread is published2chat.getChannel("support").async { channelResult ->3 channelResult.onSuccess { channel ->4 // get the last message on the channel, which is the root message for the thread5 channel.getHistory(null, null, 1).async { historyResult ->6 historyResult.onSuccess { history ->7 val message = history.messages[0]8 9 // get the thread channel10 message.getThread().async { threadChannelResult ->11 threadChannelResult.onSuccess { threadChannel ->12 // get the last message on the thread channel13 threadChannel.getHistory(null, null, 1).async { threadHistoryResult ->14 threadHistoryResult.onSuccess { threadHistory ->15 val threadMessage = threadHistory.messages[0]16 17 // unpin the last message from the parent "support" channel18 threadMessage.unpinFromParentChannel().async { unpinResult ->19 unpinResult.onSuccess { parentChannel ->20 // handle success (parentChannel refers to the parent "support" channel without the unpinned message)21 }.onFailure { unpinError ->22 // Handle failure23 }24 }25 }.onFailure { threadHistoryError ->26 // handle failure27 }28 }29 }.onFailure { threadChannelError ->30 // handle failure31 }32 }33 }.onFailure { historyError ->34 // handle failure35 }36 }37 }.onFailure { channelError ->38 // handle failure39 }40}

## Create thread message draft

`createThreadMessageDraft()` creates a new thread for a message and returns a `MessageDraft` for composing and sending the first message in that thread, with options like user suggestions and typing indicators.

### Method signature

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

### Input parameters

| Parameter | Description |
| --- | --- |
| `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

| Type | Description |
| --- | --- |
| `PNFuture<MessageDraft>` | Object used for composing and formatting a message draft in a thread channel. |

### Sample code

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

```kotlin
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
                }
            }
        } else {
            // Handle no messages found
        }
    }.onFailure {
        // Handle failure in fetching history
    }
}
```

## Create thread (deprecated)

:::warning Deprecated
This method is deprecated. Use [Create thread](#create-thread) instead.
:::

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

#### Method signature

This method has the following signature:

```kotlin
message.createThread(): PNFuture<ThreadChannel>
```

##### Input

This method doesn't take any parameters.

##### Output

| Type | Description |
| --- | --- |
| `PNFuture<ThreadChannel>` | Object returning the thread channel metadata for the message updated with the `hasThread` parameter (and a `threadRootId` action type underneath). |

### Sample code

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

```kotlin
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
        }
    }.onFailure {
        // handle failure
    }
}
```