> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-feature-android-pin-save-thread.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Save A Message

Let users bookmark messages for later. Saving a message is **private to the logged-in user** — nobody else in the conversation can see it — and works **across conversations**: a user's saved messages from all of their chats appear in one list, synced across all of their devices. Let's see how to work with saved messages in CometChat's Android SDK.

<Note>
  Saving a message with the SDK requires the Save Message feature to be enabled for your app. You can check its availability at runtime using the [feature flag](#feature-availability).
</Note>

## Save a Message

To save a message, use the `saveMessage` method and pass the ID of the message. On success, the callback returns the updated `BaseMessage` with its `savedAt` attribute set.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    long messageId = 1;

    CometChat.saveMessage(messageId, new CometChat.CallbackListener<BaseMessage>() {
      @Override
      public void onSuccess(BaseMessage message) {
          Log.d(TAG, "Message saved at: " + message.getSavedAt());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Failed to save message: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val messageId = 1L

    CometChat.saveMessage(messageId, object : CometChat.CallbackListener<BaseMessage>() {
      override fun onSuccess(message: BaseMessage?) {
          Log.d(TAG, "Message saved at: ${message?.savedAt}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Failed to save message: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

<Info>
  Unlike pinning, saving has no role restrictions — every user can save any message they have access to. Deleted messages cannot be saved.
</Info>

## Unsave a Message

To remove a message from the user's saved list, use the `unsaveMessage` method. On success, the callback returns the updated `BaseMessage` with its `savedAt` attribute cleared.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    long messageId = 1;

    CometChat.unsaveMessage(messageId, new CometChat.CallbackListener<BaseMessage>() {
      @Override
      public void onSuccess(BaseMessage message) {
          Log.d(TAG, "Message unsaved. isSaved: " + message.isSaved());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Failed to unsave message: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val messageId = 1L

    CometChat.unsaveMessage(messageId, object : CometChat.CallbackListener<BaseMessage>() {
      override fun onSuccess(message: BaseMessage?) {
          Log.d(TAG, "Message unsaved. isSaved: ${message?.isSaved}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Failed to unsave message: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

## Fetch Saved Messages

To fetch all messages the logged-in user has saved, create a `MessagesRequest` with the `setSaved(true)` filter of the `MessagesRequestBuilder`. Because saved messages are user-level and span conversations, you must **not** set a `UID` or `GUID`. The returned list is sorted by the time of saving, most recently saved first.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder()
      .setSaved(true)
      .setLimit(50)
      .build();

    messagesRequest.fetchNext(new CometChat.CallbackListener<List<BaseMessage>>() {
      @Override
      public void onSuccess(List<BaseMessage> messages) {
          Log.d(TAG, "Saved messages: " + messages.size());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Saved messages fetch failed: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val messagesRequest = MessagesRequest.MessagesRequestBuilder()
      .setSaved(true)
      .setLimit(50)
      .build()

    messagesRequest.fetchNext(object : CometChat.CallbackListener<List<BaseMessage>>() {
      override fun onSuccess(messages: List<BaseMessage>?) {
          Log.d(TAG, "Saved messages: ${messages?.size}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Saved messages fetch failed: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

<Info>
  If the user loses access to a conversation (for example, they are removed from a group), messages saved from it are cleaned up and no longer returned.
</Info>

## Check if a Message is Saved

Every fetched message carries the logged-in user's save state on the `BaseMessage` itself. These values are **per-user**: the same message shows different values to different users.

| Method         | Description                                                                            |
| -------------- | -------------------------------------------------------------------------------------- |
| `isSaved()`    | Returns `true` if the logged-in user has saved this message.                           |
| `getSavedAt()` | The timestamp at which the logged-in user saved the message. `0` when it is not saved. |

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    if (message.isSaved()) {
        Log.d(TAG, "Saved at " + message.getSavedAt());
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    if (message.isSaved) {
        Log.d(TAG, "Saved at ${message.savedAt}")
    }
    ```
  </Tab>
</Tabs>

## Real-time Save Events

Because saving is private, save events are never delivered to other participants. Register a `MessageListener` and override the save callbacks; each event delivers the full updated `BaseMessage`.

Today these callbacks fire on the **acting device** when a save or unsave succeeds. Delivery to the user's other devices activates once server-side real-time delivery for save events is rolled out — until then, other sessions pick up save changes on their next fetch.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    private String listenerID = "UNIQUE_LISTENER_ID";

    CometChat.addMessageListener(listenerID, new CometChat.MessageListener() {
      @Override
      public void onMessageSaved(BaseMessage message) {
          Log.d(TAG, "Message saved: " + message.getId());
      }

      @Override
      public void onMessageUnsaved(BaseMessage message) {
          Log.d(TAG, "Message unsaved: " + message.getId());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val listenerID = "UNIQUE_LISTENER_ID"

    CometChat.addMessageListener(listenerID, object : CometChat.MessageListener() {
      override fun onMessageSaved(message: BaseMessage) {
          Log.d(TAG, "Message saved: ${message.id}")
      }

      override fun onMessageUnsaved(message: BaseMessage) {
          Log.d(TAG, "Message unsaved: ${message.id}")
      }
    })
    ```
  </Tab>
</Tabs>

To stop listening, remove the listener with `CometChat.removeMessageListener(listenerID)`.

## Feature Availability

Check whether the Save Message feature is enabled for your app before showing save actions in your UI. The method is synchronous and safe to call from the UI layer.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    if (CometChat.isSaveMessageEnabled()) {
        // show the Save option
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    if (CometChat.isSaveMessageEnabled()) {
        // show the Save option
    }
    ```
  </Tab>
</Tabs>
