---
source_url: https://www.pubnub.com/docs/sdks/kotlin/api-reference/message-actions
title: Message Actions API for Kotlin SDK
updated_at: 2026-06-17T11:39:52.518Z
sdk_name: PubNub Kotlin SDK
sdk_version: 13.4.0
---

> 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 Actions API for Kotlin SDK

PubNub Kotlin SDK, use the latest version: 13.4.0

Install:

```bash
Add PubNub dependency to your build@13.4.0
```

:::warning Breaking changes in v9.0.0
PubNub Kotlin SDK version 9.0.0 unifies the codebases for Kotlin and [Java](https://www.pubnub.com/docs/sdks/java) SDKs, introduces a new way of instantiating the PubNub client, and changes asynchronous API callbacks and emitted [status events](https://www.pubnub.com/docs/sdks/kotlin/status-events). These changes can impact applications built with previous versions (< `9.0.0` ) of the Kotlin SDK.
For more details about what has changed, refer to [Java/Kotlin SDK migration guide](https://www.pubnub.com/docs/sdks/kotlin/migration-guides/kotlin-v9-migration-guide).
:::

Use message actions to add or remove metadata on published messages. Common uses include receipts and reactions. Clients subscribe to a channel to receive message action events. Clients can also fetch past message actions from Message Persistence, either on demand or when fetching original messages.

:::tip Request execution
Most PubNub Kotlin SDK method invocations return an Endpoint object, which allows you to decide whether to perform the operation synchronously or asynchronously.
You must invoke the `.sync()` or `.async()` method on the Endpoint to execute the request, or the operation **will not** be performed.
```kotlin
val channel = pubnub.channel("channelName")
channel.publish("This SDK rules!").async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```
:::

:::tip Reactions
"Message Reactions" is a specific application of the Message Actions API for emoji or social reactions.
:::

:::note Message Actions vs. Message Reactions
**Message Actions** is the flexible, low-level API for adding any metadata to messages (read receipts, delivery confirmations, custom data), while **Message Reactions** specifically refers to using Message Actions for emoji/social reactions.
In PubNub [Core](https://www.pubnub.com/docs/sdks) and [Chat](https://www.pubnub.com/docs/chat/overview) SDKs, the same underlying Message Actions API is referred to as **Message Reactions** when used for emoji reactions - it's the same functionality, just different terminology depending on the use case.
:::

## Add message action

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/) as described in the [support article](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Add an action to a published message. The response includes the added action.

### Method(s)

Use this Kotlin method:

```kotlin
pubnub.addMessageAction(
    channel: String,
    messageAction: PNMessageAction
).async { result -> }
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel | String | Yes |  | Channel name to add the message action to. |
| messageAction | PNMessageAction | Yes |  | Message action payload (type, value, message timetoken). |

### Sample code

:::tip Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
:::

```kotlin
import com.pubnub.api.PubNub
import com.pubnub.api.UserId
import com.pubnub.api.models.consumer.message_actions.PNMessageAction
import com.pubnub.api.v2.PNConfiguration

fun main() {
    println("PubNub addMessageAction() Example")
    println("================================")

    // 1. Configure PubNub
    val userId = UserId("message-action-demo-user")
    val config = PNConfiguration.builder(userId, "demo").apply {
        publishKey = "demo"
    }.build()

    // 2. Create PubNub instance
    val pubnub = PubNub.create(config)

    try {
        // In a real app, you would have the timetoken of a message you want to react to
        // For this example, we'll use a sample timetoken
        val messageTimetoken = 15701761818730000L
        val channel = "demo-channel"

        println("Working with message with timetoken: $messageTimetoken")

        // First, check if there are any existing message actions we need to clean up
        println("\nChecking for existing message actions...")

        pubnub.getMessageActions(
            channel = channel
        ).async { getResult ->
            getResult.onSuccess { response ->
                // Check if there are any existing message actions for our message
                val existingActions = response.actions.filter {
                    it.messageTimetoken?.toLong() == messageTimetoken &&
                        it.type == "reaction" &&
                        it.value == "smiley_face"
                }

                if (existingActions.isNotEmpty()) {
                    println("Found ${existingActions.size} existing message actions. Removing them...")

                    // Remove each existing action
                    var actionsRemoved = 0
                    existingActions.forEach { action ->
                        pubnub.removeMessageAction(
                            channel = channel,
                            messageTimetoken = action.messageTimetoken?.toLong() ?: 0L,
                            actionTimetoken = action.actionTimetoken?.toLong() ?: 0L
                        ).async { removeResult ->
                            removeResult.onSuccess {
                                actionsRemoved++
                                println("Removed message action with timetoken: ${action.actionTimetoken}")

                                // When all actions are removed, add the new action
                                if (actionsRemoved == existingActions.size) {
                                    addNewMessageAction(pubnub, channel, messageTimetoken)
                                }
                            }.onFailure { exception ->
                                println("Error removing message action: ${exception.message}")
                                // Try to add the new action anyway
                                addNewMessageAction(pubnub, channel, messageTimetoken)
                            }
                        }
                    }
                } else {
                    println("No existing message actions found.")
                    addNewMessageAction(pubnub, channel, messageTimetoken)
                }
            }.onFailure { exception ->
                println("Error checking message actions: ${exception.message}")
                // Try to add the new action anyway
                addNewMessageAction(pubnub, channel, messageTimetoken)
            }
        }

        // Keep the program running to allow async operations to complete
        println("\nWaiting for async operations to complete...")
        Thread.sleep(10000)
    } catch (e: Exception) {
        println("Error: ${e.message}")
        e.printStackTrace()
    }
}

/**
 * Helper function to add a new message action
 */
fun addNewMessageAction(pubnub: PubNub, channel: String, messageTimetoken: Long) {
    println("\nAdding a new message action...")

    // Create a message action
    val messageAction = PNMessageAction(
        type = "reaction", // Type of reaction (can be any string)
        value = "smiley_face", // Value of reaction (can be any string)
        messageTimetoken = messageTimetoken // Timetoken of the message to add the reaction to
    )

    // Add the message action
    pubnub.addMessageAction(
        channel = channel,
        messageAction = messageAction
    ).async { result ->
        result.onSuccess { response ->
            println("\nMessage action added successfully!")
            println("  • Type: ${response.type}")
            println("  • Value: ${response.value}")
            println("  • Publisher UUID: ${response.uuid}")
            println("  • Message Timetoken: ${response.messageTimetoken}")
            println("  • Action Timetoken: ${response.actionTimetoken}")

            println("\nFor reference, to get all message actions, you would use:")
            println("pubnub.getMessageActions(")
            println("    channel = \"$channel\"")
            println("    // Optional: add paging parameters")
            println(")")

            // Clean up resources
            pubnub.destroy()
        }.onFailure { exception ->
            println("Error adding message action: ${exception.message}")
            exception.printStackTrace()
            pubnub.destroy()
        }
    }
}
```

### Returns

The `addMessageAction()` operation returns a `PNAddMessageActionResult`:

| Method | Description |
| --- | --- |
| `type`Type: `String` | Message action type. |
| `value`Type: `String` | Message action value. |
| `uuid`Type: `String` | Publisher of the message action. |
| `actionTimetoken`Type: `String` | Timestamp when the message action was created. |
| `messageTimetoken`Type: `Long` | Timestamp when the message was created that the action belongs to. |

#### PNMessageAction

| Method | Description |
| --- | --- |
| `type`Type: `String` | Message action type. |
| `value`Type: `String` | Message action value. |
| `messageTimetoken`Type: `Long` | Timetoken of the target message. |

## Remove message action

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/) as described in the [support article](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Remove a previously added action from a published message. The response is empty.

### Method(s)

Use this Kotlin method:

```kotlin
pubnub.removeMessageAction(
    channel: String,
    messageTimetoken: Long,
    actionTimetoken: Long
).async { result -> }
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `String` | Channel name to remove the message action from. |
| `messageTimetoken` *Type: `Long` | Timetoken of the target message. |
| `actionTimetoken` *Type: `Long` | Timetoken of the message action to remove. |

### Sample code

```kotlin
pubnub.removeMessageAction(
    channel = "my_channel",
    messageTimetoken = 15701761818730000L,
    actionTimetoken = 15701775691010000L
).async { result: Result<PNRemoveMessageActionResult> ->
    result.onSuccess { res: PNRemoveMessageActionResult ->
        // result has no actionable data
        // it's enough to check if the status itself is not an error
    }.onFailure { e: PubNubException ->
        // do something with the exception
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

### Returns

The `removeMessageAction()` operation returns no actionable data.

## Get message actions

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/) as described in the [support article](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Get a list of message actions in a channel. The response sorts actions by the action timetoken in ascending order.

### Method(s)

Use this Kotlin method:

```kotlin
pubnub.getMessageActions(
    channel: String,
    page: PNBoundedPage
)
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `String` | Channel name to list message actions for. |
| `page`Type: `PNBoundedPage` | Paging object. Set `limit` to specify the number of actions to return (default/maximum is `100`). Use `start` and `end` to set range boundaries (results are < `start` and ≥ `end`). |

### Sample code

```kotlin
pubnub.getMessageActions(
    channel = "my_channel"
).async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```

### Returns

The `getMessageActions()` operation returns a list of `PNGetMessageActionsResult?` objects:

| Method | Description |
| --- | --- |
| `type`Type: `String` | Message action type. |
| `value`Type: `String` | Message action value. |
| `uuid`Type: `String` | Publisher of the message action. |
| `actionTimetoken`Type: `String` | Timestamp when the message action was created. |
| `messageTimetoken`Type: `Long` | Timestamp when the message was created that the action belongs to. |
| `page`Type: `PNBoundedPage` | If present, more data is available. Pass it to another `getMessageActions` call to retrieve remaining data. |

### Other examples

#### Fetch messages with paging

```kotlin
import com.pubnub.api.PubNub
import com.pubnub.api.PubNubException
import com.pubnub.api.UserId
import com.pubnub.api.models.consumer.PNBoundedPage
import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult
import com.pubnub.api.models.consumer.message_actions.PNMessageAction
import com.pubnub.api.v2.PNConfiguration

fun main() {
    val userId = UserId("message-action-demo-user")
    val config = PNConfiguration.builder(userId, "demo").apply {
        publishKey = "demo"
    }.build()

    val pubnub = PubNub.create(config)
    val channelName = "my_channel"
    val channel = pubnub.channel(channelName)

    val pnPublishResult = channel.publish(message = "Hello, world!").sync()

    pubnub.addMessageAction(
        channel = channelName,
        messageAction =
            PNMessageAction(
                type = "someother",
                value = "smiley",
                messageTimetoken = pnPublishResult.timetoken,
            ),
    ).sync()

    getMessageActionsWithPaging(
        pubNub = pubnub,
        channel = channelName,
        start = null // Start with no start value to get the most recent actions
    ) { actions ->
        println("Fetched ${actions.size} message actions:")
        actions.forEach {
            println("Action Type: ${it.type}")
            println("Action Value: ${it.value}")
            println("UUID: ${it.uuid}")
            println("Message Timetoken: ${it.messageTimetoken}")
            println("Action Timetoken: ${it.actionTimetoken}")
        }
    }
}

/**
 * Fetches 5 message reactions at a time, recursively and in a paged manner.
 *
 * @param channel  The channel where the message is published, to fetch message reactions from.
 * @param start    The timetoken which indicates from where to start fetching message reactions.
 * @param callback The callback to dispatch fetched message reactions to.
 */
fun getMessageActionsWithPaging(
    pubNub: PubNub,
    channel: String,
    start: Long?,
    callback: (actions: List<PNMessageAction>) -> Unit
) {
    val page = start?.let { PNBoundedPage(limit = 5, start = it) } ?: PNBoundedPage(limit = 5)
    pubNub.getMessageActions(
        channel = channel,
        page = page
    ).async { result ->
        result.onSuccess { getMessageActionsResult: PNGetMessageActionsResult ->
            if (getMessageActionsResult.actions.isNotEmpty()) {
                callback.invoke(getMessageActionsResult.actions)
                getMessageActionsWithPaging(
                    pubNub,
                    channel,
                    getMessageActionsResult.actions.first().actionTimetoken,
                    callback
                )
            } else {
                callback.invoke(emptyList())
            }
        }.onFailure { exception: PubNubException ->
            println("Error fetching message actions: $exception")
        }
    }
}
```

## Terms in this document

* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Channel pattern** - A way to group and analyze channel data to track performance metrics like message counts and user engagement over time with PubNub Insights.