# Manage server-side messages (/en/realtime-media/im/build/build-core-messaging/messages/retrieve-messages)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="[&#x22;android&#x22;,&#x22;ios&#x22;,&#x22;web&#x22;,&#x22;flutter&#x22;,&#x22;react-native&#x22;,&#x22;windows&#x22;,&#x22;unity&#x22;]" showTabs="true">
  <_PlatformPanel platform="android">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    ## Prerequisites [#prerequisites]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server]

    Call `asyncFetchConversationsFromServer` to retrieve conversations from the server with pagination. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation). In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `getAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `ChatOptions#isLoadEmptyConversations` to `true` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```java
    String cursor = "";
    limit: The number of conversations that you expect to get on each page. The value range is [1,50].
    int limit = 40;
    List conversations = new ArrayList<>();
    doAsyncFetchConversationsFromServer(limit,cursor,conversations);

    private void doAsyncFetchConversationsFromServer(final int limit, final String cursor,List conversations){
        ChatClient.getInstance().chatManager().asyncFetchConversationsFromServer(limit, cursor, new ValueCallBack>() {
            @Override
            public void onSuccess(CursorResult value) {
                if (value != null ) {
                    List list = value.getData();
                    if (list != null && list.size() > 0) {
                        conversations.addAll(list);
                    }
                    String newCursor = value.getCursor();
                    if( !TextUtils.isEmpty(newCursor)) {
                        doAsyncFetchConversationsFromServer(limit, newCursor, conversations);
                    }
                }

            }

            @Override
            public void onError(int error, String errorMsg) {

            }
        });
    }
    ```

    ### Retrieve message history of the specified conversation [#retrieve-message-history-of-the-specified-conversation]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, by default, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, and this number can be adjusted to 200 messages/chatroom without additional charges.

    After the GA release of Agora Chat 1.3, you can log in to Agora Console and enable the chatroom message history retrieval feature. After that, Agora Chat servers will stop automatically pushing chatroom messages to new members and you can follow the guidance below to retrieve chatroom message history.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by specific members (rather than all members) by setting the sender list with `FetchMessageOption#setFromIds`.

    Refer to the following code sample:

    ```java
    String conversationId=" ";
    Conversation.ConversationType type=Conversation.ConversationType.Chat;
    FetchMessageOption option=new FetchMessageOption();
    //For example, save the retrieved messages to the local database.
    //option.setIsSave(true);
    //Since v1.4.0, for a group conversation you can retrieve messages sent by specific members.
    //For example, retrieve messages sent by two specific user IDs in the group.
    //List<String> fromIds = new ArrayList<String>();
    //fromIds.add("user1");
    //fromIds.add("user2");
    //option.setFromIds(fromIds);
    int pageSize = 40;
    String cursor = "";
    List messages = new ArrayList<>();
    doAsyncFetchHistoryMessages(conversationId,type,pageSize,cursor,option,messages);

    private void doAsyncFetchHistoryMessages(String conversationId,
                                             Conversation.ConversationType type,
                                             int pageSize,String cursor,
                                             FetchMessageOption option,
                                             List messages){
        ChatClient.getInstance().chatManager().asyncFetchHistoryMessages(conversationId, type, pageSize, cursor, option, new ValueCallBack>() {
            @Override
            public void onSuccess(CursorResult value) {
                if (value != null ) {
                    List list = value.getData();
                    if (list != null && list.size() > 0) {
                        messages.addAll(list);
                    }
                    String newCursor = value.getCursor();
                    if( !TextUtils.isEmpty(newCursor)) {
                        doAsyncFetchHistoryMessages(conversationId, type, pageSize, newCursor, option, messages);
                    }
                }
            }

            @Override
            public void onError(int error, String errorMsg) {

            }
        });
    }
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members]

    Since SDK v1.4.0, for a single conversation you can search the local database for messages that contain a keyword and are sent by specific members. You can specify up to 10 senders; to search messages from all senders, pass `null` or an empty list.

    ```java
    String conversationId = "user_or_group_id";
    Conversation conversation = ChatClient.getInstance()
            .chatManager()
            .getConversation(conversationId);
    if (conversation != null) {
        String keywords = "hello";
        long timeStamp = -1; // A value less than 0 means the search starts from the current time.
        int maxCount = 20;
        // Limit the senders to a maximum of 10. To not limit the senders, pass null or an empty list.
        List<String> senders = Arrays.asList("user1", "user2");
        conversation.asyncSearchMsgFromDB(
                keywords,
                timeStamp,
                maxCount,
                senders,
                Conversation.SearchDirection.UP,
                Conversation.ChatMessageSearchScope.CONTENT,
                new ValueCallBack<List<ChatMessage>>() {
                    @Override
                    public void onSuccess(List<ChatMessage> messages) {
                        for (ChatMessage message : messages) {
                            String msgId = message.getMsgId();
                            String from = message.getFrom();
                            long msgTime = message.getMsgTime();
                            // TODO: handle the search results
                        }
                    }

                    @Override
                    public void onError(int code, String error) {
                        // TODO: handle the error
                    }
                }
        );
    }
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword]

    Since SDK v1.4.0, you can search across all local conversations for messages that contain a keyword. The SDK returns a map of conversation IDs to the lists of matching message IDs. Each message ID list is ordered by message timestamp, in ascending or descending order according to the `direction` parameter.

    ```java
    String keyword = "Time";
    ChatClient.getInstance().chatManager().asyncLoadConversationMessagesWithKeyword(keyword, -1, null, Conversation.SearchDirection.UP, Conversation.ChatMessageSearchScope.CONTENT, new ValueCallBack<Map<String, List<String>>>() {
        @Override
        public void onSuccess(Map<String, List<String>> value) {
            EMLog.e(TAG, "asyncLoadConversationMessagesWithKeyword onSuccess value:" + value);
        }

        @Override
        public void onError(int error, String errorMsg) {
            EMLog.e(TAG, "asyncLoadConversationMessagesWithKeyword onError error:" + error + " errorMsg:" + errorMsg);
        }
    });
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id]

    Since SDK v1.4.0, you can pass one or more message IDs to retrieve messages from a single local conversation. You can pass a maximum of 20 message IDs at a time.

    ```java
    // messageIds: the list of message IDs. You can pass a maximum of 20 message IDs at a time.
    ChatClient.getInstance().chatManager().asyncLoadMessages(messageIds, conversationId, new ValueCallBack<List<ChatMessage>>() {
        @Override
        public void onSuccess(List<ChatMessage> value) {
            EMLog.e(TAG, "asyncLoadMessages onSuccess value:" + value);
        }

        @Override
        public void onError(int error, String errorMsg) {
            EMLog.e(TAG, "asyncLoadMessages onError error:" + error + " errorMsg:" + errorMsg);
        }
    });
    ```

    ### Pin a conversation [#pin-a-conversation]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```java
    ChatClient.getInstance().chatManager().asyncPinConversation(conversationId, true, new CallBack() {
        @Override
        public void onSuccess() {
        }

        @Override
        public void onError(int code, String error) {
        }
    });
    ```

    ### Pin a message [#pin-a-message]

    You can call `ChatManager#asyncPinMessage` to pin a message to the top of a one-to-one chat, chat group, or chat room. When the pinned status of a message changes, other members in the group or chat room conversation will receive the `MessageListener#onMessagePinChanged` event. In the case of multi-device login, the updated top status will be synchronized to other logged-in devices, and other devices will receive the `MessageListener#onMessagePinChanged` event, respectively.

    In group and chat room conversations, multiple users can pin the same message to the top. The latest pinned message will overwrite the earlier information. That is, the `MessagePinInfo` user ID and pin time will correspond to the latest pinned message.

    If the message is stored locally but deleted on the server due to expiration, the message will fail to be pinned to the top.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```java
    ChatClient.getInstance().chatManager().asyncPinMessage(message.getMsgId(), new CallBack() {
        @Override
        public void onSuccess() {

        }

        @Override
        public void onError(int code, String error) {

        }

        @Override
        public void onProgress(int progress, String status) {

        }
    });

    ```

    ### Unpin a message [#unpin-a-message]

    You can call `ChatManager#asyncUnPinMessage` to unpin a message in a one-to-one chat, chat group, or chat room. As with pinned messages, other members of the group or chat room will receive the `MessageListener#onMessagePinChanged` event when the pinned message is unpinned. In the case of multi-device login, the updated pin status will be synchronized to other logged-in devices, and other devices will receive the `MessageListener#onMessagePinChanged` event, respectively.

    Any user in a group or room can unpin a message, regardless of who pinned it. After unpinning a message, `Message#pinnedInfo` is returned empty and the message is no longer included in the pinned message list.

    ```java
    ChatClient.getInstance().chatManager().asyncUnPinMessage(message.getMsgId(), new CallBack() {
        @Override
        public void onSuccess() {

        }

        @Override
        public void onError(int code, String error) {

        }

        @Override
        public void onProgress(int progress, String status) {

        }
    });
    ```

    ### Get pinned messages in a single conversation [#get-pinned-messages-in-a-single-conversation]

    You can call `ChatManager#asyncGetPinnedMessagesFromServer` to get the pinned messages from a single conversation from the server. The SDK returns messages in the reverse order of pinning.

    <CalloutContainer type="success">
      <CalloutDescription>
        If a message expires on the server or the user deletes it unilaterally from the server after pinning, such user will not be able to pull it when pulling roaming messages. However, this user and other users will be able to pull this message from the pinned message list.If the user withdraws a message after pinning, the message will be removed from the server; other users will not be able to pull it when they pull the pinned message list from the server.
      </CalloutDescription>
    </CalloutContainer>

    ```java
    ChatClient.getInstance().chatManager().asyncGetPinnedMessagesFromServer(conversationId, new ValueCallBack>() {
        @Override
        public void onSuccess(List pinedMessages) {

        }

        @Override
        public void onError(int error, String errorMsg) {

        }
    });
    ```

    ### Get pinned details of a single message [#get-pinned-details-of-a-single-message]

    You can call `MessagePinInfo` to get the pinned details of a single message.

    * If the message is pinned, this class returns the time of pinning and the user ID of the user who has pinned it.
    * If the message is not pinned, this class is returned empty.

    ```java
    MessagePinInfo emPinnedInfo = message.pinnedInfo();
    if(emPinnedInfo!=null) {
        long pinTime = emPinnedInfo.pinTime();
        String operatorId = emPinnedInfo.operatorId();
    }else{
        //If the value is empty, the message is in the unpinned state.
    }
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally]

    Call `removeMessagesFromServer` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    ```java
    // Delete messages by time stamp
    Conversation conversation = ChatClient.getInstance().chatManager().getConversation(username);
    conversation.removeMessagesFromServer(time, new CallBack() {
        @Override
        public void onSuccess() {}

        @Override
        public void onError(int code, String desc) {}
    });

    // Delete messages by message ID
    conversation.removeMessagesFromServer(msgIdList, new CallBack() {
        @Override
        public void onSuccess() { }

        @Override
        public void onError(int code, String desc) { }
    });
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally]

    Call `deleteConversationFromServer` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on your local device, but the messages are automatically removed from the device. Other chat users can still get the conversations and their historical messages from the server.

    ```java
    ChatClient.getInstance().chatManager().deleteConversationFromServer(conversationId, conversationType, isDeleteServerMessage, new CallBack() {
        @Override
        public void onSuccess() {

        }

        @Override
        public void onError(int code, String error) {
        }
    });
    ```

    ### Tag a conversation [#tag-a-conversation]

    To tag a conversation, use the `asyncAddConversationMark` method. This method allows you to add tags to both local and server-side conversations. Each conversation can have up to 20 tags. This feature applies only to private chats and chat groups.

    After adding a tag, you can retrieve the tagged conversations from the server by calling the `getConversationsFromServerWithCursor:pageSize:completion` method. The returned conversation objects will include the conversation tag, which you can obtain using the `marks` property.

    If the server conversation list reaches its limit (default 100 conversations per user), inactive conversations will be deleted based on conversation activity (timestamp of the latest message). Consequently, the conversation tags of these deleted conversations will also be removed.

    <CalloutContainer type="info">
      <CalloutDescription>
        Adding tags to a conversation, such as stars, does not affect other logic of the conversation, such as the number of unread messages in it.
      </CalloutDescription>
    </CalloutContainer>

    ```java
    String conversationId = "Derry";
    List ids=new ArrayList<>();
    ids.add(conversationId);
    ChatClient.getInstance().chatManager().asyncAddConversationMark(ids, Conversation.EMMarkType.MARK_0, new CallBack() {
        @Override
        public void onSuccess() {

        }

        @Override
        public void onError(int code, String error) {

        }
    });
    ```

    To use customized tags, use the following code:

    ```java
    Map mapping=new HashMap<>();
    mapping.put(Conversation.EMMarkType.MARK_0,"important");
    mapping.put(Conversation.EMMarkType.MARK_1,"normal");
    mapping.put(Conversation.EMMarkType.MARK_2,"unimportant");
    mapping.put(Conversation.EMMarkType.MARK_3,"boys");
    mapping.put(Conversation.EMMarkType.MARK_4,"girls");
    ……
    ```

    ### Remove conversation tag [#remove-conversation-tag]

    You can call `asyncRemoveConversationMark` to remove tag of a conversation. Tags can be removed for up to 20 conversations at a time. Calling this method removes both the local and server-side tags.

    ```java
    String conversationId = "Derry";
    List ids=new ArrayList<>();
    ids.add(conversationId);
    ChatClient.getInstance().chatManager().asyncRemoveConversationMark(ids, Conversation.EMMarkType.MARK_0,new CallBack() {
        @Override
        public void onSuccess() {

        }

        @Override
        public void onError(int code, String error) {

        }
    });
    ```

    ### Query conversations from the server by the conversation tag [#query-conversations-from-the-server-by-the-conversation-tag]

    You can use the `asyncGetConversationsFromServerWithCursor` method to retrieve a list of conversations from the server based on the tag. The SDK returns the conversation list in reverse order of the conversation tag's timestamp. Each conversation object includes the following information:

    * Conversation ID
    * Conversation type
    * Pinned status (whether it is pinned or not)
    * Pin time (For an unpinned conversation, the value is 0)
    * Conversation tag
    * Latest message

    After fetching the conversation list from the server, the local conversation list is updated accordingly:

    ```java
    //All the query results are put into `result`.
    List result = new ArrayList<>();
    //cursor: The starting position of the query. Pass in an empty string for the first call of the method and the SDK starts to get from the conversation with the latest marked operation.
    String cursor = "";
    // filter: Conversation query options, including conversation marks and the number of conversations obtained per page (up to 10).
    // For example, query all conversations marked with Conversation.EMMarkType.MARK_0 on the server.
    ConversationFilter filter = new ConversationFilter(Conversation.EMMarkType.MARK_0, 10);
    doAsyncGetConversationsFromServerWithCursor(result, cursor, filter);

    private void doAsyncGetConversationsFromServerWithCursor(List result, @NonNull String cursor, @NonNull ConversationFilter filter) {
        ChatClient.getInstance().chatManager().asyncGetConversationsFromServerWithCursor(cursor, filter, new ValueCallBack>() {
            @Override
            public void onSuccess(CursorResult value) {
                List datas=value.getData();
                if(!CollectionUtils.isEmpty(datas)){
                    result.addAll(datas);
                }
                String cursor_ = value.getCursor();
                if(!TextUtils.isEmpty(cursor_)){
                    doAsyncGetConversationsFromServerWithCursor(result,cursor_,filter);
                }
            }

            @Override
            public void onError(int error, String errorMsg) {

            }
        });
    }
    ```

    ### Query local conversations by the conversation tag [#query-local-conversations-by-the-conversation-tag]

    For local conversations, you can call the `getAllConversations` method to obtain all local conversations and then perform conversation filtering yourself. The following is an example of querying all local conversations marked with `Conversation.EMMarkType.MARK_0`:

    ```java
    //All the query results are put into result.
    List result=new ArrayList<>();
    Map localConversations = ChatClient.getInstance().chatManager().getAllConversations();
    if(localConversations!=null&&!localConversations.isEmpty()){
        for(Conversation conversation:localConversations.values()){
            Set marks = conversation.marks();
            if(marks!=null&&!marks.isEmpty()){
                for(Conversation.EMMarkType mark:marks){
                    if(mark==Conversation.EMMarkType.MARK_0){
                        result.add(conversation);
                    }
                }
            }
        }
    }
    ```

    ### Get all tags of a local conversation [#get-all-tags-of-a-local-conversation]

    You can call `Conversation#marks` to get all the tags of a local conversation. The sample code is as follows:

    ```java
    Set conversationMarks = ChatClient.getInstance().chatManager().getConversation("conversationId").marks();
    ```

    ## Next steps [#next-steps]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="ios">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-1]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `IAgoraChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `getConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `fetchMessagesFromServerBy`: Retrieves historical messages of a conversation from the server according to `AgoraChatFetchServerMessagesOption`, the parameter configuration class for retrieving historical messages.
    * `pinConversation`: Pins a conversation.
    * `getPinnedConversationsFromServerWithCursor`: Retrieves a list of pinned conversations.
    * `pinMessage`: Pins a message in a conversation.
    * `unpinMessage`: Unpin a message in a conversation.
    * `getPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServerWithTimeStamp`/`removeMessagesFromServerMessageIds`: Deletes historical messages from the server unidirectionally.
    * `deleteServerConversation`: Deletes conversations and their historical messages from the server.
    * `addConversationMark`: Tags a conversation.
    * `removeConversationMark`: Removes a conversation tag.
    * `getConversationsFromServer`: Queries conversations from the server by a conversation tag.

    ## Prerequisites [#prerequisites-1]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-1]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-1]

    Call `getConversationsFromServerWithCursor:pageSize:completion` to retrieve conversations from the server with pagination. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation). In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `getAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `EMOptions#loadEmptyConversations` to `YES` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```objc
    //pageSize: The number of conversations that you expect to get on each page. The value range is [1,50].
    [AgoraChatClient.sharedClient.chatManager getConversationsFromServerWithCursor:@"" pageSize:20 completion:^(AgoraChatCursorResult * _Nullable result, AgoraChatError * _Nullable error) {

    }];
    ```

    ### Retrieve historical messages of the specified conversation [#retrieve-historical-messages-of-the-specified-conversation]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, by default. This number can be adjusted to 200 messages/chatroom without additional charges.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by specific members (rather than all members) by setting the `fromIds` list in `AgoraChatFetchServerMessagesOption`.

    Refer to the following code sample:

    ```objc
    AgoraChatFetchServerMessagesOption* option = [[AgoraChatFetchServerMessagesOption alloc] init];
        // Since SDK v1.4.0, for a group conversation you can retrieve messages sent by specific members.
        option.fromIds = @[@"user1", @"user2"];
        [AgoraChatClient.sharedClient.chatManager fetchMessagesFromServerBy:@"conversationId" conversationType:AgoraChatConversationTypeGroupChat cursor:@"" pageSize:20 option:option completion:^(AgoraChatCursorResult * _Nullable result, AgoraChatError * _Nullable aError) {

        }];
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members-1]

    Since SDK v1.4.0, for a single conversation you can search the local database for messages that contain a keyword and are sent by specific members.

    ```objc
    AgoraChatConversation *conversation = [AgoraChatClient.sharedClient.chatManager getConversationWithConvId:@"conversationId"];
    if (conversation) {
        [conversation loadMessagesWithKeyword:nil timestamp:-1 count:20 fromUsers:@[@"user1", @"user2"] searchDirection:AgoraChatMessageSearchDirectionUp scope:AgoraChatMessageSearchScopeAll completion:^(NSArray<AgoraChatMessage *> * _Nullable aMessages, AgoraChatError * _Nullable aError) {
            if (aError == nil) {
                // Loaded successfully.
            }
        }];
    }
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword-1]

    Since SDK v1.4.0, you can search across all local conversations for messages that contain a keyword. The SDK returns a dictionary of conversation IDs to the lists of matching message IDs. Each message ID list is ordered by message timestamp, in ascending or descending order according to the `aDirection` parameter.

    ```objc
    [AgoraChatClient.sharedClient.chatManager loadConversationMessagesWithKeyword:@"keyword" timestamp:-1 fromUser:@"" searchDirection:AgoraChatMessageSearchDirectionUp scope:AgoraChatMessageSearchScopeAll completion:^(NSDictionary<NSString *,NSArray<NSString *> *> * _Nullable aConversationMessages, AgoraChatError * _Nullable aError) {
        if (aError == nil) {
            // aConversationMessages contains the matching conversation IDs and message IDs.
        }
    }];
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id-1]

    Since SDK v1.4.0, you can pass one or more message IDs to retrieve messages from a single local conversation. You can pass a maximum of 20 message IDs at a time.

    ```objc
    // You can pass a maximum of 20 message IDs at a time.
    [AgoraChatClient.sharedClient.chatManager getMessages:@[@"messageId1", @"messageId2"] withConversationId:@"conversationId" completion:^(NSArray<AgoraChatMessage *> * _Nullable aMessages, AgoraChatError * _Nullable aError) {
        if (aError == nil) {
            // aMessages contains the retrieved messages.
        }
    }];
    ```

    ### Pin a conversation [#pin-a-conversation-1]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```objc
    [AgoraChatClient.sharedClient.chatManager pinConversation:@"conversationId" isPinned:YES completionBlock:^(AgoraChatError * _Nullable error) {

    }];
    ```

    ### Retrieve the pinned conversations from the server with pagination [#retrieve-the-pinned-conversations-from-the-server-with-pagination]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```objc
    // pageSize: Number of sessions returned per page. The value range is [1,50].
    // cursor: The starting position of the query. If `nil` or `@""` is passed in, the SDK will start querying from the latest pinned session.
    [AgoraChatClient.sharedClient.chatManager getPinnedConversationsFromServerWithCursor:@"" pageSize:20 completion:^(AgoraChatCursorResult * _Nullable result, AgoraChatError * _Nullable error) {

    }];
    ```

    ### Pin a message [#pin-a-message-1]

    You can call `ChatManager#pinMessage` to pin a message to the top of a one-to-one chat, chat group, or chat room. When the pinned status of a message changes, other members in the group or chat room conversation will receive the `ChatManagerDelegate#onMessagePinChanged` event. In the case of multi-device login, the updated top status will be synchronized to other logged-in devices, and other devices will receive the `ChatManagerDelegate#onMessagePinChanged` event, respectively.

    In group and chat room conversations, multiple users can pin the same message to the top. The latest pinned message will overwrite the earlier information. That is, the `MessagePinInfo` user ID and pin time will correspond to the latest pinned message.

    If the message is stored locally but deleted on the server due to expiration, the message will fail to be pinned to the top.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```swift
    ChatClient.shared().chatManager?.pinMessage("messageId", completion: { message, err in
        if err == nil {
            // Pinned successfully
        }
    })
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-1]

    Call `removeMessagesFromServerWithTimeStamp` or `removeMessagesFromServerMessageIds` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    Other devices logged in to the account will receive the `MultiDevicesDelegate` callback `multiDevicesMessageBeRemoved`, and the deleted messages will be automatically removed from the device locally.

    ```objc
    // Delete messages by timestamp
    AgoraChatConversation* conversation = [AgoraChatClient.sharedClient.chatManager getConversationWithConvId:@"conversationId"];
        [conversation removeMessagesFromServerWithTimeStamp:timeToRemove completion:^(AgoraChatError * _Nullable aError) {

        }];
    // Delete messages by message ID
     [conversation removeMessagesFromServerMessageIds:@[@"msgId1",@"msgId2"] completion:^(AgoraChatError * _Nullable aError) {

        }];
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-1]

    Call `deleteServerConversation` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on the local device, but the messages are automatically removed from the device. Other chat users can still get the conversations and their historical messages from the server.

    ```objc
    [AgoraChatClient.sharedClient.chatManager deleteServerConversation:@"conversationId1" conversationType:AgoraChatConversationTypeChat isDeleteServerMessages:YES completion:^(NSString *aConversationId, AgoraChatError *aError) {

        }];
    ```

    ## Next steps [#next-steps-1]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="web">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-2]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `getServerConversations`: Retrieves a list of conversations stored on the server with pagination.
    * `getHistoryMessages`: Retrieves the historical messages in the specified conversation from the server.
    * `pinConversation`: Pin a conversation.
    * `getServerPinnedConversations`: Retrieves a list of pinned conversations.
    * `pinMessage`: Pins a message in a conversation.
    * `unpinMessage`: Unpin a message in a conversation.
    * `getServerPinnedMessages`: Get a list of pinned messages in a conversation.
    * `removeHistoryMessages`: Deletes historical messages from the server unidirectionally.
    * `deleteConversation`: Deletes conversations and their historical messages from the server.
    * `addConversationMark`: Tags a conversation.
    * `removeConversationMark`: Removes a conversation tag.
    * `getServerConversationsByFilter`: Queries conversations from the server by a conversation tag.

    ## Prerequisites [#prerequisites-2]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-2]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-2]

    Call `getServerConversations` to retrieve conversations from the server with pagination. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation). In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included. However, if there are empty conversations on the server, they will occupy the conversation list quota. To change this, contact [support@agora.io](mailto\:support@agora.io).

    <CalloutContainer type="info">
      <CalloutDescription>
        Do not use mixed upper-case.
      </CalloutDescription>
    </CalloutContainer>

    ```javascript
    // pageSize: The number of conversations that you expect to get on each page. The value range is [1,50] and the default value is `20`.
    // cursor: The cursor position for starting to get data. If you pass in an empty string (''), the SDK retrieves conversations from the latest active one.
    chatClient.getServerConversations({ pageSize: 50, cursor: "" }).then((res) => {
      console.log(res);
    });
    ```

    ### Retrieve message history of the specified conversation [#retrieve-message-history-of-the-specified-conversation-1]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, by default, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, and this number can be adjusted to 200 messages/chatroom without additional charges.

    After the GA release of Agora Chat 1.3, you can log in to Agora Console and enable the chatroom message history retrieval feature. After that, Agora Chat servers will stop automatically pushing chatroom messages to new members and you can follow the guidance below to retrieve chatroom message history.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by a specific member by setting `from` in `searchOptions`.

    Refer to the following code sample:

    ```javascript
    chatClient.getHistoryMessages({
      targetId: "targetId", // The user ID of the peer user for one-to-one chat or group ID for group chat.
      chatType: "groupChat", // The chat type: `singleChat` for one-to-one chat or `groupChat` for group chat.
      pageSize: 20, // The number of messages to retrieve per page. The value range is [1,50] and the default value is 20.
      searchDirection: "down", // The message search direction: `up` means to retrieve messages in the descending order of the message timestamp and `down` means to retrieve messages in the ascending order of the message timestamp.
      searchOptions: {
        from: "message sender userID", // The user ID of the message sender. This parameter is used only for group chat.
        msgTypes: ["txt"], // An array of message types for query. If no value is passed in, all types of message will be queried.
        startTime: new Date("2023,11,9").getTime(), // The start timestamp for query. The unit is millisecond.
        endTime: new Date("2023,11,10").getTime(), // The end timestamp for query. The unit is millisecond.
      },
    });
    ```

    ### Pin a conversation [#pin-a-conversation-2]

    Refer to the following code example to pin a conversation:

    ```javascript
    chatClient.pinConversation({
      conversationId: "conversationId",
      conversationType: "singleChat",
      isPinned: true,
    });
    ```

    ### Retrieve the pinned conversations from the server with pagination [#retrieve-the-pinned-conversations-from-the-server-with-pagination-1]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```javascript
    // pageSize: The number of sessions returned per page. The value range is [1,50]。
    // cursor：The cursor position to start getting data. If an empty string ('') is passed, the SDK will start querying from the latest pinned session.
    chatClient.getServerPinnedConversations({ pageSize: 50, cursor: "" });
    ```

    ### Pin a message [#pin-a-message-2]

    You can call `pinMessage` to pin a message to the top of a one-to-one chat, chat group, or chat room. When the pinned status of a message changes, other members in the group or chat room conversation will receive the `onMessagePinEvent` event. In the case of multi-device login, the updated top status will be synchronized to other logged-in devices, and other devices will receive the `onMessagePinEvent` event, respectively.

    In group and chat room conversations, multiple users can pin the same message to the top. The latest pinned message will overwrite the earlier information. That is, the `PinnedMessageInfo` user ID and pin time will correspond to the latest pinned message.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```javascript
    const options = {
      // Conversation type which is `groupChat` for group chat and `chatRoom` for chat room chat.
      conversationType: "groupChat",
      // Conversation ID.
      conversationId: "conversationId",
      // Message ID.
      messageId: "messageId",
    };

    chatClient.pinMessage(options).then(() => {
      // Succeeded in pinning the message.
      console.log("Succeeded in pinning the message");
    });
    ```

    ### Unpin a message [#unpin-a-message-1]

    You can call `unpinMessage` to unpin a message in a one-to-one chat, chat group, or chat room. As with pinned messages, other members of the group or chat room will receive the `onMessagePinEvent` event when the pinned message is unpinned. In the case of multi-device login, the updated pin status will be synchronized to other logged-in devices, and other devices will receive the `onMessagePinEvent` event, respectively.

    Any user in a group or room can unpin a message, regardless of who pinned it. After unpinning a message, the message is no longer included in the pinned message list.

    ```javascript
    const options = {
      // Conversation type which is `groupChat` for group chat and `chatRoom` for chat room chat.
      conversationType: "groupChat",
      // Conversation ID.
      conversationId: "conversationId",
      // Message ID.
      messageId: "messageId",
    };

    chatClient.unpinMessage(options).then(() => {
      // Succeeded in unpinning the message.
      console.log("Succeeded in unpinning the message.");
    });
    ```

    ### Get pinned messages in a single conversation [#get-pinned-messages-in-a-single-conversation-1]

    You can call `getServerPinnedMessages` to get the pinned messages from a single conversation from the server. The SDK returns messages in the reverse order of pinning.

    <CalloutContainer type="success">
      <CalloutDescription>
        If a message expires on the server or the user deletes it unilaterally from the server after pinning, such user will not be able to pull it when pulling roaming messages. However, this user and other users will be able to pull this message from the pinned message list.

        If the user withdraws a message after pinning, the message will be removed
        from the server; other users will not be able to pull it when they pull
        the pinned message list from the server.
      </CalloutDescription>
    </CalloutContainer>

    ```javascript
    const options = {
      // Conversation ID.
      conversationId: "conversationId",
      // Conversation type which is `groupChat` for group chat and `chatRoom` for chat room chat.
      conversationType: "groupChat",
      // The number of pinned messages to retrieve per page. The value range is [1,50] and the default value is `10`
      pageSize: 20,
      // The cursor position that indicates where to start getting data.
      // Pass in an empty string ('') for the first call of the API and the SDK returns the pinned messages in the descending order of the pinning time.
      cursor: "",
    };

    chatClient.getServerPinnedMessages(options).then((res) => {
      // Succeeded in retrieving the list of pinned messages in a conversation.
      console.log(res);
    });
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-2]

    Call `removeHistoryMessages` to delete historical messages one way from the server. You can remove a maximum of 20 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. Other chat users can still get the messages from the server.

    ```javascript
    // Delete messages by timestamp
    chatClient.removeHistoryMessages({
      targetId: "userId",
      chatType: "singleChat",
      beforeTimeStamp: Date.now(),
    });
    // Delete messages by message ID
    chatClient.removeHistoryMessages({
      targetId: "userId",
      chatType: "singleChat",
      messageIds: ["messageId"],
    });
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-2]

    You can use the `removeHistoryMessages` method to unidirectionally delete historical messages on the server based on time or message ID. Up to 20 messages can be deleted at once. After deletion, the account cannot retrieve the message from the server. Other users are not affected by this operation.

    Upon successful deletion, the `deleteRoaming` callback will be triggered when logging in from multiple terminals and devices.

    ```javascript
    // Delete messages by time stamp
    chatClient.removeHistoryMessages({
      targetId: "userId",
      chatType: "singleChat",
      beforeTimeStamp: Date.now(),
    });

    // Delete messages by message ID
    chatClient.removeHistoryMessages({
      targetId: "userId",
      chatType: "singleChat",
      messageIds: ["messageId"],
    });
    ```

    ### Tag a conversation [#tag-a-conversation-1]

    To tag a conversation, use the `addConversationMark` method. This method allows you to add tags to both local and server-side conversations. Each conversation can have up to 20 tags. This feature applies only to private chats and chat groups.

    After adding a tag, you can retrieve the tagged conversations from the server by calling the `getServerConversations` method. The returned conversation objects will include the conversation tag, which you can obtain using the `ConversationItem#marks` method.

    If the server conversation list reaches its limit (default 100 conversations per user), inactive conversations will be deleted based on conversation activity (timestamp of the latest message). Consequently, the conversation tags of these deleted conversations will also be removed.

    <CalloutContainer type="info">
      <CalloutDescription>
        Adding tags to a conversation, such as stars, does not affect other logic of
        the conversation, such as the number of unread messages in it.
      </CalloutDescription>
    </CalloutContainer>

    ```javascript
    const options = {
      conversations: [
        { conversationId: "test", conversationType: "singleChat" },
        { conversationId: "groupId", conversationType: "groupChat" },
      ],
      mark: 0,
    };

    chatClient.addConversationMark(options).then(() => {
      console.log("addConversationMark success");
    });
    ```

    To create customized tags, you need to do the following:

    ```javascript
    const MarkMap = new Map();
    MarkMap.set(0, "IMPORTANT");
    MarkMap.set(1, "NORMAL");
    MarkMap.set(2, "STAR");
    ```

    ### Remove conversation tag [#remove-conversation-tag-1]

    To remove a conversation tag, call `removeConversationMark`. Calling this method removes both the local and server-side conversation tags.

    ```javascript
    const options = {
      conversations: [
        { conversationId: "test", conversationType: "singleChat" },
        { conversationId: "groupId", conversationType: "groupChat" },
      ],
      mark: 0,
    };

    chatClient.removeConversationMark(options).then(() => {
      console.log("removeConversationMark success");
    });
    ```

    ### Query conversations from the server by the conversation tag [#query-conversations-from-the-server-by-the-conversation-tag-1]

    You can use the `getServerConversationsByFilter` method to retrieve a list of conversations from the server based on the tag. The SDK returns the conversation list in the reverse order of the conversation tag's timestamp. Each conversation object includes the following information:

    * Conversation ID
    * Conversation type
    * Pinned status (whether it is pinned or not)
    * Pin time (For an unpinned conversation, the value is 0)
    * Conversation tag
    * Latest message

    After fetching the conversation list from the server, the local conversation list is updated accordingly:

    ```javascript
    const options = {
      pageSize: 10,
      cursor: "",
      filter: {
        mark: 0,
      },
    };
    chatClient.getServerConversationsByFilter().then((res) => {
      console.log("getServerConversationsByFilter success", res);
    });
    ```

    ## Next steps [#next-steps-2]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="flutter">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-3]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `fetchHistoryMessagesByOption`: Retrieves historical messages of a conversation from the server according to `FetchMessageOptions`, the parameter configuration class for retrieving historical messages.
    * `pinConversation`: Pins a conversation.
    * `deleteRemoteMessagesBefore`/`deleteRemoteMessagesWithIds`: Deletes historical messages from the server unidirectionally.
    * `deleteRemoteConversation`: Deletes conversations and their historical messages from the server.
    * `addRemoteAndLocalConversationsMark`: Tags a conversation.
    * `deleteRemoteAndLocalConversationsMark`: Removes a conversation tag.
    * `fetchConversationsByOptions`: Queries conversations from the server by parameters.

    ## Prerequisites [#prerequisites-3]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-3]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-3]

    Call `fetchConversationsByOptions` to retrieve conversations from the server with pagination. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation). In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `loadAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `EMOptions#enableEmptyConversation` to `true` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```dart
    ChatClient.getInstance.chatManager.fetchConversationsByOptions(
          options: ConversationFetchOptions(),
        );
    ```

    ### Retrieve message history of the specified conversation [#retrieve-message-history-of-the-specified-conversation-2]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, by default, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, and this number can be adjusted to 200 messages/chatroom without additional charges.

    After the GA release of Agora Chat 1.3, you can log in to Agora Console and enable the chatroom message history retrieval feature. After that, Agora Chat servers will stop automatically pushing chatroom messages to new members and you can follow the guidance below to retrieve chatroom message history.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Refer to the following code sample:

    ```dart
    ChatConversationType conversationType = ChatConversationType.Chat;
    FetchMessageOptions options = FetchMessageOptions(
      from: senderId,
      // Since SDK v1.4.0, for a group conversation you can retrieve messages sent by specific members.
      senders: ['senderA', 'senderB'],
      direction: ChatSearchDirection.Up,
      startTs: 1695720454000,
      endTs: 1695720554000,
      needSave: false,
    );

    ChatCursorResult result =
        await ChatClient.getInstance.chatManager.fetchHistoryMessagesByOption(
      conversationId,
      conversationType,
      options: options,
      cursor: cursor,
      pageSize: pageSize,
    );
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members-2]

    Since SDK v1.4.0, for a single conversation you can search the local database for messages that contain a keyword and are sent by specific members.

    ```dart
    List<ChatMessage> list = await conversation.loadMessagesWithKeyword(
      keywords,
      senders: ['senderA', 'senderB'],
    );
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword-2]

    Since SDK v1.4.0, you can search across all local conversations for messages that contain a keyword. The SDK returns a map of conversation IDs to the lists of matching message IDs. Each message ID list is ordered by message timestamp, in ascending or descending order according to the `direction` parameter.

    ```dart
    Map<String, List<String>> result =
        await ChatClient.getInstance.chatManager.loadConversationMessagesWithKeyword(
      keyword: 'hello',
      timestamp: -1,
      sender: null,
      direction: ChatSearchDirection.Up,
      scope: MessageSearchScope.All,
    );
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id-2]

    Since SDK v1.4.0, you can pass one or more message IDs to retrieve messages from a single local conversation. You can pass a maximum of 20 message IDs at a time.

    ```dart
    // messageIdList: the list of message IDs. You can pass a maximum of 20 message IDs at a time.
    List<ChatMessage> messages =
        await ChatClient.getInstance.chatManager.loadMessagesWithIds(
      messageIdList,
      conversationId,
    );
    ```

    ### Pin a conversation [#pin-a-conversation-3]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```dart
    ChatClient.getInstance.chatManager.pinConversation(
      conversationId: conversationId,
      isPinned: isPinned,
    );
    ```

    ### Retrieve the pinned conversations from the server with pagination [#retrieve-the-pinned-conversations-from-the-server-with-pagination-2]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```dart
    ChatClient.getInstance.chatManager.fetchConversationsByOptions(
          options: ConversationFetchOptions.pinned(),
        );
    ```

    ### Pin a message [#pin-a-message-3]

    You can call `ChatManager#pinMessage` to pin a message to the top of a one-to-one chat, chat group, or chat room. When the pinned status of a message changes, other members in the group or chat room conversation will receive the `ChatEventHandler#onMessagePinChanged` event. In the case of multi-device login, the updated top status will be synchronized to other logged-in devices, and other devices will receive the `ChatEventHandler#onMessagePinChanged` event, respectively.

    In group and chat room conversations, multiple users can pin the same message to the top. The latest pinned message will overwrite the earlier information. That is, the `MessagePinInfo` user ID and pin time will correspond to the latest pinned message.

    If the message is stored locally but deleted on the server due to expiration, the message will fail to be pinned to the top.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```dart
    try {
      await ChatClient.getInstance.chatManager.pinMessage(messageId: msgId);
    } on ChatError catch (e) {
      debugPrint('pinMessage error: ${e.code} ${e.description}');
    }
    ```

    ### Unpin a message [#unpin-a-message-2]

    You can call `ChatManager#unpinMessage` to unpin a message in a one-to-one chat, chat group, or chat room. As with pinned messages, other members of the group or chat room will receive the `ChatEventHandler#onMessagePinChanged` event when the pinned message is unpinned. In the case of multi-device login, the updated pin status will be synchronized to other logged-in devices, and other devices will receive the `ChatEventHandler#onMessagePinChanged` event, respectively.

    Any user in a group or room can unpin a message, regardless of who pinned it. After unpinning a message, `Message#pinnedInfo` is returned empty and the message is no longer included in the pinned message list.

    ```dart
      try {
      await ChatClient.getInstance.chatManager.unpinMessage(messageId: msgId);
    } on ChatError catch (e) {
      debugPrint('unpinMessage error: ${e.code} ${e.description}');
    }
    ```

    ### Get pinned messages in a single conversation [#get-pinned-messages-in-a-single-conversation-2]

    You can call `ChatManager#fetchPinnedMessages` to get the pinned messages from a single conversation from the server. The SDK returns messages in the reverse order of pinning.

    <CalloutContainer type="success">
      <CalloutDescription>
        If a message expires on the server or the user deletes it unilaterally from the server after pinning, such user will not be able to pull it when pulling roaming messages. However, this user and other users will be able to pull this message from the pinned message list.If the user withdraws a message after pinning, the message will be removed from the server; other users will not be able to pull it when they pull the pinned message list from the server.
      </CalloutDescription>
    </CalloutContainer>

    ```dart
    try {
      List pinnedMessages = await ChatClient.getInstance.chatManager.fetchPinnedMessages(
        conversationId: conversationId,
      );
    } on ChatError catch (e) {
      debugPrint('fetchPinnedMessages error: ${e.code} ${e.description}');
    }
    ```

    ### Get pinned details of a single message [#get-pinned-details-of-a-single-message-1]

    You can call `MessagePinInfo` to get the pinned details of a single message.

    * If the message is pinned, this class returns the time of pinning and the user ID of the user who has pinned it.
    * If the message is not pinned, this class is returned empty.

    ```dart
    try {
      MessagePinInfo? pinInfo = await message.pinInfo();
    } on ChatError catch (e) {
      debugPrint('pinInfo error: ${e.code} ${e.description}');
    }
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-3]

    Call `deleteRemoteMessagesBefore` or `deleteRemoteMessagesWithIds` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    ```dart
    try {
      // Delete messages by timestamp
      await ChatClient.getInstance.chatManager.deleteRemoteMessagesBefore(
        conversationId: conversationId,
        type: convType,
        timestamp: timestamp,
      );
    } on ChatError catch (e) {}
    try {
      // Delete messages by message ID
      await ChatClient.getInstance.chatManager.deleteRemoteMessagesWithIds(
        conversationId: conversationId,
        type: convType,
        msgIds: msgIds,
      );
    } on ChatError catch (e) {}
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-3]

    Call `deleteRemoteConversation` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on the local device, but the messages are automatically removed from your local device. Other chat users can still get the conversations and their historical messages from the server.

    ```dart
    try {
      await ChatClient.getInstance.chatManager.deleteRemoteConversation(
        conversationId,
      );
    } on ChatError catch (e) {}
    ```

    ### Tag a conversation [#tag-a-conversation-2]

    To tag a conversation, use the `addRemoteAndLocalConversationsMark` method. This method allows you to add tags to both local and server-side conversations. Each conversation can have up to 20 tags. This feature applies only to private chats and chat groups.

    After adding a tag, you can retrieve the tagged conversations from the server by calling the `fetchConversationsByOptions` method. The returned conversation objects will include the conversation tag, which you can obtain using the `marks` property.

    If the server conversation list reaches its limit (default 100 conversations per user), inactive conversations will be deleted based on conversation activity (timestamp of the latest message). Consequently, the conversation tags of these deleted conversations will also be removed.

    <CalloutContainer type="info">
      <CalloutDescription>
        Adding tags to a conversation, such as stars, does not affect other logic of the conversation, such as the number of unread messages in it.
      </CalloutDescription>
    </CalloutContainer>

    ```dart
    try {
      await EMClient.getInstance.chatManager.addRemoteAndLocalConversationsMark(
        conversationIds: conversationsIds,
        mark: ConversationMarkType.Type0,
      );
    } on EMError catch (e) {
      debugPrint("addRemoteAndLocalConversationsMark error: ${e.code}, ${e.description}");
    }

    ```

    ### Remove conversation tag [#remove-conversation-tag-2]

    You can call `deleteRemoteAndLocalConversationsMark` to remove tag of a conversation. Tags can be removed for up to 20 conversations at a time. Calling this method removes both the local and server-side tags.

    ```dart
    try {
      await ChatClient.getInstance.chatManager.deleteRemoteAndLocalConversationsMark(
        conversationIds: conversationIds,
        mark: ConversationMarkType.Type0,
      );
    } on ChatError catch (e) {
      debugPrint('deleteRemoteAndLocalConversationsMark error: ${e.code} ${e.description}');
    }
    ```

    ### Query conversations from the server by the conversation tag [#query-conversations-from-the-server-by-the-conversation-tag-2]

    You can use the `fetchConversationsByOptions` method to retrieve a list of conversations from the server based on the tag. The SDK returns the conversation list in reverse order of the conversation tag's timestamp. Each conversation object includes the following information:

    * Conversation ID
    * Conversation type
    * Pinned status (whether it is pinned or not)
    * Pin time (For an unpinned conversation, the value is 0)
    * Conversation tag
    * Latest message

    After fetching the conversation list from the server, the local conversation list is updated accordingly:

    ```dart
    try {
      ChatCursorResult result = await ChatClient.getInstance.chatManager.fetchConversationsByOptions(
        options: ConversationFetchOptions.mark(
          ConversationMarkType.Type0,
        ),
      );
    } on ChatError catch (e) {
      debugPrint('fetchConversationsByOptions error: ${e.code} ${e.description}');
    }
    ```

    ### Query local conversations by the conversation tag [#query-local-conversations-by-the-conversation-tag-1]

    For local conversations, you can call the `getAllConversations` method to obtain all local conversations and then perform conversation filtering yourself. The following is an example of querying all local conversations marked with `EMConversation.EMMarkType.MARK_0`:

    ```dart
    try {
      List markedConversations = [];
      List conversations = await ChatClient.getInstance.chatManager.loadAllConversations();
      for (var conversation in conversations) {
        if (conversation.marks?.contains(ConversationMarkType.Type0) == true) {
          markedConversations.add(conversation);
        }
      }
      debugPrint('marked conversations: $markedConversations');
    } on ChatError catch (e) {
      debugPrint('get marks conversations error: ${e.code} ${e.description}');
    }
    ```

    ### Get all tags of a local conversation [#get-all-tags-of-a-local-conversation-1]

    You can call `Conversation#marks` to get all the tags of a local conversation. The sample code is as follows:

    ```dart
    try {
      ChatConversation? conversation = await ChatClient.getInstance.chatManager.getConversation(targetId);
      List? markType = conversation?.marks;
    } on ChatError catch (e) {
      debugPrint('get marks error: ${e.code} ${e.description}');
    }
    ```

    ## Next steps [#next-steps-3]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="react-native">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-4]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `fetchAllConversations`: Retrieves a list of conversations stored on the server.
    * `fetchHistoryMessagesByOptions`: Retrieves historical messages of a conversation from the server according to `ChatFetchMessageOptions`, the parameter configuration class for retrieving historical messages.
    * `pinConversation`: Pins a conversation.
    * `fetchPinnedConversationsFromServerWithCursor`: Retrieves a list of pinned conversations.
    * `removeMessagesFromServerWithTimestamp`/`removeMessagesFromServerWithMsgIds`: Deletes historical messages from the server unidirectionally.
    * `removeConversationFromServer`: Deletes conversations and related messages from the server.

    ## Prerequisites [#prerequisites-4]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-4]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-4]

    Call `fetchConversationsFromServerWithCursor` to retrieve conversations from the server with pagination. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation). In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `getAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `ChatOptions#enableEmptyConversation` to `true` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```java
    // pageSize: The number of conversations that you expect to get on each page. The value range is [1,50].
    // cursor: If `cursor` is an empty string, the SDK retrieves from the latest conversation.
    ChatClient.getInstance()
      .chatManager.fetchConversationsFromServerWithCursor(cursor, pageSize)
      .then(() => {
        console.log("get conversions success");
      })
      .catch((reason) => {
        console.log("get conversions fail.", reason);
      });
    ```

    If you do not support `fetchConversationsFromServerWithCursor`, call `fetchConversationsFromServerWithPage` to retrieve the conversations from the server. Altogether, the SDK can retrieve the last 100 conversations in the past seven days. To adjust the time limit or the number of conversations retrieved, contact [support@agora.io](mailto\:support@agora.io).

    ### Retrieve historical messages of the specified conversation [#retrieve-historical-messages-of-the-specified-conversation-1]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, by default. This number can be adjusted to 200 messages/chatroom without additional charges.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by specific members (rather than all members) by setting the `senders` array in `ChatFetchMessageOptions`.

    Refer to the following code sample:

    ```typescript
    ChatClient.getInstance()
      .chatManager.fetchHistoryMessagesByOptions(convId, convType, {
        cursor: cursor,
        pageSize: pageSize,
        options: options as ChatFetchMessageOptions,
      })
      .then((result) => {
        console.log("get history message success", result);
      })
      .catch((reason) => {
        console.log("get history message fail.", reason);
      });
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members-3]

    Since SDK v1.4.0, for a single conversation you can load messages from the local database that are sent by specific members, using `getConvMsgsWithKeyword`.

    ```typescript
    const conversationId = '<YOUR_CONVERSATION_ID>';
    const conversationType = ChatConversationType.GroupChat;
    const senders = ['user1', 'user2'];
    ChatClient.getInstance()
      .chatManager.getConvMsgsWithKeyword({
        convId: conversationId,
        convType: conversationType,
        senders: senders,
        keywords: '',
      })
      .then((messages) => console.log('Messages:', messages))
      .catch((error) => console.error('Error:', error));
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword-3]

    Since SDK v1.4.0, you can call `getConvsMsgsWithKeyword` to search across all local conversations for messages that contain a keyword. The SDK returns the matching conversation IDs and message IDs, ordered by message timestamp in ascending or descending order according to the `direction` parameter.

    ```typescript
    ChatClient.getInstance()
      .chatManager.getConvsMsgsWithKeyword({
        keywords: 'hello',
        timestamp: -1,
        from: '<MESSAGE_SENDER_ID>',
        direction: ChatSearchDirection.UP,
        searchScope: ChatMessageSearchScope.All,
      })
      .then((result) => console.log('Result:', result))
      .catch((error) => console.error('Error:', error));
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id-3]

    Since SDK v1.4.0, you can call `getMessagesWithIds` to retrieve one or more messages from a single local conversation by message ID.

    ```typescript
    ChatClient.getInstance()
      .chatManager.getMessagesWithIds({
        convId: '<YOUR_CONVERSATION_ID>',
        convType: ChatConversationType.GroupChat,
        msgIds: ['<MSG_ID_1>', '<MSG_ID_2>'],
      })
      .then((messages) => console.log('Messages:', messages))
      .catch((error) => console.error('Error:', error));
    ```

    ### Pin a conversation [#pin-a-conversation-4]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```typescript
    // isPinned: Sets whether to pin a conversation.
    ChatClient.getInstance()
      .chatManager.pinConversation(convId, isPinned)
      .then(() => {
        console.log("pin conversions success");
      })
      .catch((reason) => {
        console.log("pin conversions fail.", reason);
      });
    ```

    ### Retrieve the pinned conversations from the server with pagination [#retrieve-the-pinned-conversations-from-the-server-with-pagination-3]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```typescript
    // pageSize: The number of sessions returned per page. The value range is [1,50]。
    // cursor：The cursor position to start getting data.
    ChatClient.getInstance()
      .chatManager.fetchPinnedConversationsFromServerWithCursor(cursor, pageSize)
      .then(() => {
        console.log("get conversions success");
      })
      .catch((reason) => {
        console.log("get conversions fail.", reason);
      });
    ```

    ### Pin a message [#pin-a-message-4]

    You can call `ChatManager#pinMessage` to pin a message to the top of a one-to-one chat, chat group, or chat room. When the pinned status of a message changes, other members in the group or chat room conversation will receive the `MessageListener#onMessagePinChanged` event. In the case of multi-device login, the updated top status will be synchronized to other logged-in devices, and other devices will receive the `MessageListener#onMessagePinChanged` event, respectively.

    In group and chat room conversations, multiple users can pin the same message to the top. The latest pinned message will overwrite the earlier information. That is, the `ChatMessagePinInfo` user ID and pin time will correspond to the latest pinned message.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```typescript
    ChatClient.getInstance()
      .chatManager.pinMessage(
        messageId // message ID
      )
      .then(() => {
        // todo: operation completed
      })
      .catch((error) => {
        // todo: operation failed
      });
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-4]

    Call `removeMessagesFromServerWithTimestamp` or `removeMessagesFromServerWithMsgIds` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    <CalloutContainer type="info">
      <CalloutDescription>
        To use this function, you need to contact [support@agora.io](mailto\:support@agora.io) to enable it.
      </CalloutDescription>
    </CalloutContainer>

    ```typescript
    // Delete messages by message ID
    ChatClient.getInstance()
      .chatManager.removeMessagesFromServerWithMsgIds(convId, convType, msgIds)
      .then((result) => {
        console.log("test:success:", result);
      })
      .catch((error) => {
        console.warn("test:error:", error);
      });
    // Delete messages by timestamp
    ChatClient.getInstance()
      .chatManager.removeMessagesFromServerWithTimestamp(
        convId,
        convType,
        timestamp
      )
      .then((result) => {
        console.log("test:success:", result);
      })
      .catch((error) => {
        console.warn("test:error:", error);
      });
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-4]

    Call `removeConversationFromServer` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on the local device, but the messages are automatically removed from the device. Other chat users can still get the conversations and their historical messages from the server.

    ```typescript
    // convId: conversation ID
    // convType: conversation type.
    // isDeleteMessage: Whether to delete historical messages from the server and local storage with the conversation.
    ChatClient.getInstance()
      .chatManager.removeConversationFromServer(convId, convType, isDeleteMessage)
      .then(() => {
        console.log("remove conversions success");
      })
      .catch((reason) => {
        console.log("remove conversions fail.", reason);
      });
    ```

    ## Next steps [#next-steps-4]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="windows">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-5]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `IChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `GetConversationsFromServerWithPage`: Retrieve conversations stored on the server with pagination.
    * `FetchHistoryMessagesFromServerBy`: Retrieves historical messages of a conversation from the server according to `FetchServerMessagesOption`, the parameter configuration class for retrieving historical messages.
    * `PinConversation`: Pins a conversation.
    * `GetConversationsFromServerWithCursor`: Retrieves a list of pinned conversations.
    * `RemoveMessagesFromServer`: Deletes historical messages from the server unidirectionally.
    * `DeleteConversationFromServer`: Deletes conversations and their historical messages from the server.

    ## Prerequisites [#prerequisites-5]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-5]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-5]

    Call `GetConversationsFromServerWithCursor` to retrieve conversations from the server with pagination. You can set the  `pinOnly` parameter in this method to determine whether to retrieve the list of pinned conversations:

    * If `pinOnly` is set to `false`, you can retrieve the list of pinned and unpinned conversations. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation).
    * If `pinOnly` is set to `true`, you can only retrieve the list of pinned conversations. The SDK returns the pinned conversations in the reverse chronological order of when they were pinned. Up to 50 pinned conversations can be retrieved.

    In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `LoadAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `Options#EnableEmptyConversation` to `true` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```csharp
    // limit: The number of conversations that you expect to get on each page. The value range is [1,50].
    int limit = 10;
    string cursor = "";
    bool pinOnly = false; // `false`：Get all conversations; `true`: Get the list of pinned conversations.
    SDKClient.Instance.ChatManager.GetConversationsFromServerWithCursor(pinOnly, cursor, limit, new ValueCallBack>(
       onSuccess: (result) =>
       {
           // Traverse the obtained conversation list
           foreach (var conv in result.Data)
           {

           }
           // The cursor for the next request
           string nextCursor = result.Cursor;
       },
       onError: (code, desc) =>
       {

       }
    ));
    ```

    ### Retrieve historical messages of the specified conversation [#retrieve-historical-messages-of-the-specified-conversation-2]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, by default. This number can be adjusted to 200 messages/chatroom without additional charges.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by specific members (rather than all members) by setting the `FromIds` list in `FetchServerMessagesOption`. `FromIds` replaces the deprecated `From` attribute.

    Refer to the following code sample:

    ```csharp
    FetchServerMessagesOption option = new FetchServerMessagesOption();
    option.IsSave = false;
    option.Direction = MessageSearchDirection.UP;
    // Since SDK v1.4.0, for a group conversation, set FromIds to retrieve messages sent by specific members.
    option.FromIds = new List<string> { "id1", "id2" };
    option.MsgTypes = new List();
    option.MsgTypes.Add(MessageBodyType.TXT);
    option.MsgTypes.Add(MessageBodyType.VIDEO);
    option.StartTime = 1695720454000;
    option.EndTime = 1695720554000;

    SDKClient.Instance.ChatManager.FetchHistoryMessagesFromServerBy(conversationId, type, cursor, pageSize, option, new ValueCallBack>(
        onSuccess: (result) =>
        {

        },
        onError: (code, desc) =>
        {

        }
    ));
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members-4]

    Since SDK v1.4.0, for a single conversation you can load messages from the local database that are sent by specific members.

    ```csharp
    Conversation conv = SDKClient.Instance.ChatManager.GetConversation(conversationId, convType);
    conv.LoadMessagesWithScopeAndFromIds(keyword, timestamp, maxCount, fromIds, direction, scope, new ValueCallBack<List<Message>>(
        onSuccess: (list) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword-4]

    Since SDK v1.4.0, you can call `LoadConversationMessagesWithKeyword` to search across all local conversations for messages that contain a keyword. The SDK returns the matching conversation IDs and message IDs, ordered by message timestamp in ascending or descending order according to the `direction` parameter.

    ```csharp
    SDKClient.Instance.ChatManager.LoadConversationMessagesWithKeyword(keywords, timestamp, from, direction, scope, new ValueCallBack<Dictionary<string, List<string>>>(
        onSuccess: (result) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id-4]

    Since SDK v1.4.0, you can call `LoadMessages` to retrieve one or more messages from a single local conversation by message ID. To retrieve a single message, call `LoadMessage`.

    ```csharp
    List<string> messageIdList = new List<string> { "msgId1", "msgId2" };
    SDKClient.Instance.ChatManager.LoadMessages(messageIdList, conversationId, new ValueCallBack<List<Message>>(
        onSuccess: (messages) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Pin a conversation [#pin-a-conversation-5]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```csharp
    SDKClient.Instance.ChatManager.PinConversation(convId, isPinned, new CallBack(
        onSuccess: () =>
        {

        },
        onError: (code, desc) =>
        {

        }
    ));
    ```

    ### Retrieve the pinned conversations from the server [#retrieve-the-pinned-conversations-from-the-server]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    The Limit parameter specifies the number of conversations each API call can return, from a range of 1-50. Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```csharp
    // limit: The number of sessions returned per page. The value range is [1,50].
    // cursor: The cursor position to start getting data.
    int limit = 10;
    string cursor = "";
    bool pinOnly = true; // `false`: Get the list of all conversations; `true`: Get the list of only pinned conversations.
    SDKClient.Instance.ChatManager.GetConversationsFromServerWithCursor(pinOnly, cursor, limit, new ValueCallBack>(
       onSuccess: (result) =>
       {
           // Traverse the obtained conversation list
           foreach (var conv in result.Data)
           {

           }
           // The cursor for the next query
           string nextCursor = result.Cursor;
       },
       onError: (code, desc) =>
       {

       }
    ));
    ```

    ### Pin a message [#pin-a-message-5]

    To pin a message to the top of a one-to-one chat, chat group, or chat room, call `ChatManager#PinMessage` and set the `isPinned` parameter to `true`. When the pinned status of a message changes, other members in the one-to-one chat, group or chat room conversation receive the `IChatManagerDelegate#OnMessagePinChanged` event. In the case of multi-device login, the updated top status is synchronized to other logged-in devices, and other devices receive the `IChatManagerDelegate#OnMessagePinChanged` event, respectively.

    Multiple users can pin the same message to the top. The latest pinned message overwrites the earlier information. That is, the `PinnedInfo` user ID and pin time correspond to the last pinned message.

    If the message is stored locally but deleted on the server due to expiration, the message fails to be pinned to the top.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```csharp
    bool isPinned = true;
    SDKClient.Instance.ChatManager.PinMessage(msgId, isPinned, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Unpin a message [#unpin-a-message-3]

    To unpin a message in a one-to-one chat, chat group, or chat room, call `ChatManager#PinMessage` and set the `isPinned` parameter to `false`. As with pinned messages, other members of the one-to-one chat, chat group or chat room receive the `IChatManagerDelegate#OnMessagePinChanged` event when the pinned message is unpinned. In the case of multi-device login, the updated pin status is synchronized to other logged-in devices, and other devices receive the `IChatManagerDelegate#OnMessagePinChanged` event, respectively.

    Any user in a one-to-one chat, group or room can unpin a message, regardless of who pinned it. After unpinning a message, `PinnedBy` and `PinnedAt` in `Message#PinnedInfo` are returned empty and set to `0`, respectively, and the message is no longer included in the pinned message list.

    ```csharp
    bool isPinned = false;
    SDKClient.Instance.ChatManager.PinMessage(msgId, isPinned, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Get pinned messages in a single conversation [#get-pinned-messages-in-a-single-conversation-3]

    You can call `ChatManager#GetPinnedMessagesFromServer` to get the pinned messages from a single conversation from the server. The SDK returns messages in the reverse order of pinning.

    <CalloutContainer type="success">
      <CalloutDescription>
        If a message expires on the server or the user deletes it unilaterally from the server after pinning, such user will not be able to pull it when pulling roaming messages. However, this user and other users will be able to pull this message from the pinned message list.If the user withdraws a message after pinning, the message will be removed from the server; other users will not be able to pull it when they pull the pinned message list from the server.
      </CalloutDescription>
    </CalloutContainer>

    ```csharp
    SDKClient.Instance.ChatManager.GetPinnedMessagesFromServer(convId, new ValueCallBack>(
        onSuccess: (list) => {
            foreach (var it in list)
            {
                // Traverse the message list
            }
        },
        onError: (code, desc) => {
        }
    ));
    ```

    ### Get pinned details of a single message [#get-pinned-details-of-a-single-message-2]

    You can call `PinnedInfo` to get the pinned details of a single message.

    * If the message is pinned, this class returns the time of pinning and the user ID of the user who has pinned it.
    * If the message is not pinned, then `PinnedBy` is returned empty and `PinnedAt` is set to `0`.

    ```csharp
    // ...
    const msg: ChatMessage;
    const info = await msg.getPinInfo;
    // todo: Get the pin information of the message
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-5]

    Call `RemoveMessagesFromServer` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    ```csharp
    SDKClient.Instance.ChatManager.RemoveMessagesFromServer(convId, ctype, time, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    SDKClient.Instance.ChatManager.RemoveMessagesFromServer(convId, ctype, msgList, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-5]

    Call `DeleteConversationFromServer` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on your local device, but the messages are automatically removed from the device. Other chat users can still get the conversations and their historical messages from the server.

    ```csharp
    SDKClient.Instance.ChatManager.DeleteConversationFromServer(conversationId, conversationType, isDeleteServerMessages, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ## Next steps [#next-steps-5]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unity">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />

    The Chat SDK stores historical messages on the chat server. When a chat user logs in from a different device, you can retrieve the historical messages from the server, so that the user can also browse these messages on the new device. Additionally, the Chat SDK supports adding tags to conversation, with a maximum of 20 tags allowed per conversation.

    This page introduces how to use the Chat SDK to retrieve, delete, and tag messages and conversations.

    ## Understand the tech [#understand-the-tech-6]

    The Chat SDK uses `ChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `asyncFetchConversationsFromServer`: Retrieves a list of conversations stored on the server.
    * `asyncFetchHistoryMessages`: Retrieves historical messages of a conversation from the server according to `FetchMessageOption`, the parameter configuration class for retrieving historical messages.
    * `asyncPinConversation`: Pins conversations.
    * `asyncFetchPinnedConversationsFromServer`: Retrieves pinned conversations.
    * `asyncPinMessage`: Pins a message in a conversation.
    * `asyncUnPinMessage`: Unpin a message in a conversation.
    * `asyncGetPinnedMessagesFromServer`: Get a list of pinned messages in a conversation.
    * `removeMessagesFromServer`: One-way deletion of historical messages on the server based on message time or message ID.
    * `deleteConversationFromServer`: Deletes conversations and their historical messages from the server.
    * `asyncAddConversationMark`: Tags a conversation.
    * `asyncRemoveConversationMark`: Removes a conversation tag.
    * `asyncGetConversationsFromServerWithCursor`: Queries conversations from the server by a conversation tag.

    The Chat SDK uses `IChatManager` to retrieve historical messages from the server. The following are the core methods:

    * `GetConversationsFromServerWithPage`: Retrieve conversations stored on the server with pagination.
    * `FetchHistoryMessagesFromServerBy`: Retrieves historical messages of a conversation from the server according to `FetchServerMessagesOption`, the parameter configuration class for retrieving historical messages.
    * `PinConversation`: Pins a conversation.
    * `GetConversationsFromServerWithCursor`: Retrieves a list of pinned conversations.
    * `RemoveMessagesFromServer`: Deletes historical messages from the server unidirectionally.
    * `DeleteConversationFromServer`: Deletes conversations and their historical messages from the server.

    ## Prerequisites [#prerequisites-6]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).
    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-6]

    This section shows how to implement retrieving conversations and messages.

    ### Retrieve a list of conversations from the server [#retrieve-a-list-of-conversations-from-the-server-6]

    Call `GetConversationsFromServerWithCursor` to retrieve conversations from the server with pagination. You can set the  `pinOnly` parameter in this method to determine whether to retrieve the list of pinned conversations:

    * If `pinOnly` is set to `false`, you can retrieve the list of pinned and unpinned conversations. The SDK returns the conversation list in the reverse chronological order of when conversations are active (the timestamp of the last message in the conversation).
    * If `pinOnly` is set to `true`, you can only retrieve the list of pinned conversations. The SDK returns the pinned conversations in the reverse chronological order of when they were pinned. Up to 50 pinned conversations can be retrieved.

    In the conversation list, each conversation object contains the conversation ID, conversation type, whether the conversation is pinned, the pinned time (the value is 0 for an unpinned conversation), and the last message in the conversation. After the conversation list is retrieved from the server, the local conversation list will be updated accordingly. We recommend calling this method when the app is first installed, or when there is no conversation on the local device. Otherwise, you can call `LoadAllConversations` to retrieve conversations on the local device.

    For each end user, the server stores 100 conversations by default. When this limit is exceeded, new conversations will start overwriting the old ones. If the entire message history in a conversation expires, the conversation becomes empty. When pulling the conversation list from the server, these empty conversations are not included by default. To include them, set `Options#EnableEmptyConversation` to `true` when initializing the SDK. In this case, empty conversations will occupy the conversations pull quota, regardless of whether they are needed when pulling. To change this, contact [support@agora.io](mailto\:support@agora.io).

    ```csharp
    // limit: The number of conversations that you expect to get on each page. The value range is [1,50].
    int limit = 10;
    string cursor = "";
    bool pinOnly = false; // `false`：Get the list of all conversations; `true`: Get the list of only pinned conversations.
    SDKClient.Instance.ChatManager.GetConversationsFromServerWithCursor(pinOnly, cursor, limit, new ValueCallBack>(
       onSuccess: (result) =>
       {
           // Traverse the obtained conversation list
           foreach (var conv in result.Data)
           {

           }
           // The cursor for the next request
           string nextCursor = result.Cursor;
       },
       onError: (code, desc) =>
       {

       }
    ));
    ```

    ### Retrieve message history of the specified conversation [#retrieve-message-history-of-the-specified-conversation-3]

    After retrieving conversations, you can retrieve historical messages from the server.

    You can set the search direction to retrieve messages in the chronological or reverse chronological order of when the server receives them, the message type, the time period, the message sender, as well as whether to save the retrieved message to the local database.

    If you have integrated Chat SDK after June 8, 2023, you can retrieve historical messages even before joining the Chat Group. For earlier implementations, contact [support@agora.io](mailto\:support@agora.io) to enable this.

    The Agora Chat server stores the full message history for a certain period of time depending on your subscribed [Chat plan](./message-overview#limitations-of-message-storage-duration). After an end user logs back into Agora Chat, the servers automatically send offline messages to them, that is, messages transmitted when that end user was offline. Offline messages are a subset of the full message history stored on Agora Chat server. Sending only a subset of messages prevents distributing too many messages to a single device, which can overwhelm it and slow down the end user login. Agora Chat server stores and manages these offline messages for every end user in the following way:

    * 1:1 private chat: Store 500 offline messages by default;
    * Chat Group: Store 200 offline messages by default;
    * Chatroom: Doesn't store offline messages. However, by default, whenever an end user joins a chatroom, Agora Chat servers push the 10 latest messages/chatroom to them, and this number can be adjusted to 200 messages/chatroom without additional charges.

    After the GA release of Agora Chat 1.3, you can log in to Agora Console and enable the chatroom message history retrieval feature. After that, Agora Chat servers will stop automatically pushing chatroom messages to new members and you can follow the guidance below to retrieve chatroom message history.

    For users to receive more offline messages, use the client API or a webhook to sync with Agora Chat's server. End users can also store additional messages on their local database.

    To ensure data reliability, we recommend retrieving less than 50 historical messages for each method call. To retrieve more than 50 historical messages, call this method multiple times. Once the messages are retrieved, the SDK automatically updates these messages in the local database.

    We recommend that you retrieve 20 messages each time, with a maximum of 50. During paginated query, if the total number of messages that meet the query conditions is greater than the number of `pageSize`, the number of messages of `pageSize` will be returned. If it is less than the number of `pageSize`, the actual number will be returned. When the message query is completed, the number of returned messages is less than the number of `pageSize`.

    Since SDK v1.4.0, for a single group conversation you can retrieve messages sent by specific members (rather than all members) by setting the `FromIds` list in `FetchServerMessagesOption`. `FromIds` replaces the deprecated `From` attribute.

    Refer to the following code sample:

    ```csharp
    FetchServerMessagesOption option = new FetchServerMessagesOption();
    option.IsSave = false;
    option.Direction = MessageSearchDirection.UP;
    // Since SDK v1.4.0, for a group conversation, set FromIds to retrieve messages sent by specific members.
    option.FromIds = new List<string> { "id1", "id2" };
    option.MsgTypes = new List();
    option.MsgTypes.Add(MessageBodyType.TXT);
    option.MsgTypes.Add(MessageBodyType.VIDEO);
    option.StartTime = 1695720454000;
    option.EndTime = 1695720554000;

    SDKClient.Instance.ChatManager.FetchHistoryMessagesFromServerBy(conversationId, type, cursor, pageSize, option, new ValueCallBack>(

       onSuccess: (result) =>
       {
       },
       onError: (code, desc) =>

       {
       }
     )) ;
    ```

    ### Search local messages sent by specific members [#search-local-messages-sent-by-specific-members-5]

    Since SDK v1.4.0, for a single conversation you can load messages from the local database that are sent by specific members.

    ```csharp
    Conversation conv = SDKClient.Instance.ChatManager.GetConversation(conversationId, convType);
    conv.LoadMessagesWithScopeAndFromIds(keyword, timestamp, maxCount, fromIds, direction, scope, new ValueCallBack<List<Message>>(
        onSuccess: (list) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Search local conversations by keyword [#search-local-conversations-by-keyword-5]

    Since SDK v1.4.0, you can call `LoadConversationMessagesWithKeyword` to search across all local conversations for messages that contain a keyword. The SDK returns the matching conversation IDs and message IDs, ordered by message timestamp in ascending or descending order according to the `direction` parameter.

    ```csharp
    SDKClient.Instance.ChatManager.LoadConversationMessagesWithKeyword(keywords, timestamp, from, direction, scope, new ValueCallBack<Dictionary<string, List<string>>>(
        onSuccess: (result) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Retrieve local messages by message ID [#retrieve-local-messages-by-message-id-5]

    Since SDK v1.4.0, you can call `LoadMessages` to retrieve one or more messages from a single local conversation by message ID. To retrieve a single message, call `LoadMessage`.

    ```csharp
    List<string> messageIdList = new List<string> { "msgId1", "msgId2" };
    SDKClient.Instance.ChatManager.LoadMessages(messageIdList, conversationId, new ValueCallBack<List<Message>>(
        onSuccess: (messages) => { },
        onError: (code, desc) => { }
    ));
    ```

    ### Pin a conversation [#pin-a-conversation-6]

    To keep track of an important conversation, you can pin it to the top of your conversation list. You can pin up to 50 conversations. The pinned state is stored on the server. In a multi-device login use-case, if you pin or unpin a conversation, other login devices will receive the `CONVERSATION_PINNED` or `CONVERSATION_UNPINNED` events.

    Refer to the following code example to pin a conversation:

    ```csharp
    SDKClient.Instance.ChatManager.PinConversation(convId, isPinned, new CallBack(
        onSuccess: () =>
        {

        },
        onError: (code, desc) =>
        {

        }
    ));
    ```

    ### Retrieve the pinned conversations from the server with pagination [#retrieve-the-pinned-conversations-from-the-server-with-pagination-4]

    End users can pin up to 50 conversations. After you call this API, the SDK returns the pinned conversations in the reverse chronological order of when they are pinned.

    Agora Chat servers store a list of conversations that remain active in the past 7 days, regardless of Agora Chat package subscription. A conversation is considered active if it is pinned or there are new messages in a conversation.

    Refer to the following code example to get a list of pinned conversations from the server with pagination:

    ```csharp
    // limit: The number of sessions returned per page. The value range is [1,50].
    // cursor: The cursor position to start getting data.
    int limit = 10;
    string cursor = "";
    bool pinOnly = true; // `false`: Get the list of all conversations; `true`: Get the list of only pinned conversations.
    SDKClient.Instance.ChatManager.GetConversationsFromServerWithCursor(pinOnly, cursor, limit, new ValueCallBack>(
       onSuccess: (result) =>
       {
           // Traverse the obtained conversation list
           foreach (var conv in result.Data)
           {

           }
           // The cursor for the next query
           string nextCursor = result.Cursor;
       },
       onError: (code, desc) =>
       {

       }
    ));
    ```

    ### Pin a message [#pin-a-message-6]

    To pin a message to the top of a one-to-one chat, chat group, or chat room, call `ChatManager#PinMessage` and set the `isPinned` parameter to `true`. When the pinned status of a message changes, other members in the one-to-one chat, group or chat room conversation receive the `IChatManagerDelegate#OnMessagePinChanged` event. In the case of multi-device login, the updated top status is synchronized to other logged-in devices, and other devices receive the `IChatManagerDelegate#OnMessagePinChanged` event, respectively.

    Multiple users can pin the same message to the top. The latest pinned message overwrites the earlier information. That is, the `PinnedInfo` user ID and pin time correspond to the last pinned message.

    If the message is stored locally but deleted on the server due to expiration, the message fails to be pinned to the top.

    For a single conversation, 20 messages can be pinned to the top by default.

    ```csharp
    bool isPinned = true;
    SDKClient.Instance.ChatManager.PinMessage(msgId, isPinned, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Unpin a message [#unpin-a-message-4]

    To unpin a message in a one-to-one chat, chat group, or chat room, call `ChatManager#PinMessage` and set the `isPinned` parameter to `false`. As with pinned messages, other members of the one-to-one chat, chat group or chat room receive the `IChatManagerDelegate#OnMessagePinChanged` event when the pinned message is unpinned. In the case of multi-device login, the updated pin status is synchronized to other logged-in devices, and other devices receive the `IChatManagerDelegate#OnMessagePinChanged` event, respectively.

    Any user in a one-to-one chat, group or room can unpin a message, regardless of who pinned it. After unpinning a message, `PinnedBy` and `PinnedAt` in `Message#PinnedInfo` are returned empty and set to `0`, respectively, and the message is no longer included in the pinned message list.

    ```csharp
    bool isPinned = false;
    SDKClient.Instance.ChatManager.PinMessage(msgId, isPinned, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Get pinned messages in a single conversation [#get-pinned-messages-in-a-single-conversation-4]

    You can call `ChatManager#GetPinnedMessagesFromServer` to get the pinned messages from a single conversation from the server. The SDK returns messages in the reverse order of pinning.

    <CalloutContainer type="success">
      <CalloutDescription>
        If a message expires on the server or the user deletes it unilaterally from the server after pinning, such user will not be able to pull it when pulling roaming messages. However, this user and other users will be able to pull this message from the pinned message list.If the user withdraws a message after pinning, the message will be removed from the server; other users will not be able to pull it when they pull the pinned message list from the server.
      </CalloutDescription>
    </CalloutContainer>

    ```csharp
    SDKClient.Instance.ChatManager.GetPinnedMessagesFromServer(convId, new ValueCallBack>(
        onSuccess: (list) => {
            foreach (var it in list)
            {
                // Traverse the message list
            }
        },
        onError: (code, desc) => {
        }
    ));
    ```

    ### Get pinned details of a single message [#get-pinned-details-of-a-single-message-3]

    You can call `PinnedInfo` to get the pinned details of a single message.

    * If the message is pinned, this class returns the time of pinning and the user ID of the user who has pinned it.
    * If the message is not pinned, then `PinnedBy` is returned empty and `PinnedAt` is set to `0`.

    ```csharp
    // ...
    const msg: ChatMessage;
    const info = await msg.getPinInfo;
    // todo: Get the pin information of the message
    ```

    ### Delete historical messages from the server unidirectionally [#delete-historical-messages-from-the-server-unidirectionally-6]

    Call `RemoveMessagesFromServer` to delete historical messages one way from the server. You can remove a maximum of 50 messages from the server each time. Once the messages are deleted, you can no longer retrieve them from the server. The deleted messages are automatically removed from your local device. Other chat users can still get the messages from the server.

    <CalloutContainer type="info">
      <CalloutDescription>
        To use this function, you need to contact [support@agora.io](mailto\:support@agora.io) to enable it.
      </CalloutDescription>
    </CalloutContainer>

    ```csharp
    SDKClient.Instance.ChatManager.RemoveMessagesFromServer(convId, ctype, time, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    SDKClient.Instance.ChatManager.RemoveMessagesFromServer(convId, ctype, msgList, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Delete conversations and related messages from the server unidirectionally [#delete-conversations-and-related-messages-from-the-server-unidirectionally-6]

    Call `DeleteConversationFromServer` to delete conversations and their historical messages unidirectionally from the server. After the conversations and messages are deleted from the server, you can no longer get them from the server. The deleted conversations still exist on your local device, but the messages are automatically removed from the device. Other chat users can still get the conversations and their historical messages from the server.

    ```csharp
    SDKClient.Instance.ChatManager.DeleteConversationFromServer(conversationId, conversationType, isDeleteServerMessages, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Tag a conversation [#tag-a-conversation-3]

    To tag a conversation, use the `MarkConversations` method. This method allows you to add tags to both local and server-side conversations, that is, set the `isMark` parameter to 'true'. Each conversation can have up to 20 tags. This feature applies only to private chats and chat groups.

    After adding a tag, you can retrieve the tagged conversations from the server by calling the `GetConversationsFromServerWithCursor` method. The returned conversation objects will include the conversation tag, which you can obtain using the `marks` property.

    If the server conversation list reaches its limit (default 100 conversations per user), inactive conversations will be deleted based on conversation activity (timestamp of the latest message). Consequently, the conversation tags of these deleted conversations will also be removed.

    <CalloutContainer type="info">
      <CalloutDescription>
        Adding tags to a conversation, such as stars, does not affect other logic of the conversation, such as the number of unread messages in it.
      </CalloutDescription>
    </CalloutContainer>

    ```csharp
    List list = new List();
    list.Add("huanhuan");
    bool isMarked = true;
    SDKClient.Instance.ChatManager.MarkConversations(list, isMarked, (MarkType)mark, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Remove conversation tag [#remove-conversation-tag-3]

    You can call `MarkConversations` and set the `isMark` parameter to 'false' to untag a conversation. Tags can be removed for up to 20 conversations at a time. Calling this method removes both the local and server-side tags.

    ```csharp
    List list = new List();
    list.Add("huanhuan");
    bool isMarked = false;
    SDKClient.Instance.ChatManager.MarkConversations(list, isMarked, (MarkType)mark, new CallBack(
        onSuccess: () =>
        {
        },
        onError: (code, desc) =>
        {
        }
    ));
    ```

    ### Query conversations from the server by the conversation tag [#query-conversations-from-the-server-by-the-conversation-tag-3]

    You can use the `GetConversationsFromServerWithCursor` method to retrieve a list of conversations from the server based on the tag. The SDK returns the conversation list in reverse order of the conversation tag's timestamp. Each conversation object includes the following information:

    * Conversation ID
    * Conversation type
    * Pinned status (whether it is pinned or not)
    * Pin time (For an unpinned conversation, the value is 0)
    * Conversation tag
    * Latest message

    After fetching the conversation list from the server, the local conversation list is updated accordingly:

    ```csharp
    SDKClient.Instance.ChatManager.GetConversationsFromServerWithCursor((MarkType)mark, cursor, limit, new ValueCallBack>(
       onSuccess: (result) =>
       {
           foreach (var conv in result.Data)
           {
             //Iterate over the sessions in result.Data
           }
       },
       onError: (code, desc) =>
       {
       }
      ));
    ```

    ### Query local conversations by the conversation tag [#query-local-conversations-by-the-conversation-tag-2]

    For local conversations, you can call the `LoadAllConversations` method to obtain all local conversations and then perform conversation filtering yourself. The following is an example of querying all local conversations marked with `EMConversation.EMMarkType.MARK_0`:

    ```csharp
    // All final query results are put into result.
    List result = new List();
    List localConversations = SDKClient.Instance.ChatManager.LoadAllConversations();
    foreach(var conv in localConversations)
    {
        List Marks = conv.Marks();
        foreach(var mark in Marks)
        {
            if (MarkType.MarkType0 == mark)
            {
                result.Add(conv);
            }
        }
    }
    ```

    ### Get all tags of a local conversation [#get-all-tags-of-a-local-conversation-2]

    You can call `Conversation#Marks` to get all the tags of a local conversation. The sample code is as follows:

    ```csharp
    Conversation conv = SDKClient.Instance.ChatManager.GetConversation("conversationId", conversationType);
    List Marks = conv.Marks();
    ```

    ## Next steps [#next-steps-6]

    After implementing retrieving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Message receipts](./message-receipts)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>
</_PlatformTabsGroup>
