# Send and receive messages (/en/realtime-media/im/build/build-core-messaging/messages/send-receive-messages/ios)

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

After logging in to Chat, users can send the following types of messages to a peer user, a chat group, or a chat room:

    * Text messages, including hyperlinks and emojis.

    * Attachment messages, including image, voice, video, and file messages.

    * Location messages.

    * CMD messages.

    * Extended messages.

    * Custom messages.

    The Chat message feature is language agnostic. End users can send messages in any language, as long as their devices support input in that language.

    In addition to sending messages, users can also forward one or more messages. When forwarding multiple messages, users have the following options:

    * Forward messages one-by-one
    * Forward combined messages as message history

    This page shows how to implement sending, receiving, forwarding multiple messages, and modifying sent messages using the Chat SDK.

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

    The Chat SDK uses the `IAgoraChatManager` and `Message` classes to send, receive, and withdraw messages.

    The process of sending and receiving a message is as follows:

    1. The message sender creates a text, file, or attachment message using the corresponding `init` method.
    2. The message sender calls `sendMessage` to send the message.
    3. The message recipient calls `addDelegate` to listen for message events and receive the message in the `messagesDidReceive` callback.

    ## 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).

      Use Chat SDK 1.2 or higher if you intend to enable users to forward multiple messages, or to modify sent messages.

    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-1]

    This section shows how to implement sending and receiving the various types of messages.

    ### Send a text message [#send-a-text-message-1]

    Use the `AgoraChatTextMessageBody` class to create a text message, and then send the message.

    ```objc
    // Call initWithText to create a text message. Set `content` to the text content.
        AgoraChatTextMessageBody *textMessageBody = [[AgoraChatTextMessageBody alloc] initWithText:content];
        // Set `conversationId` to the user ID of the peer user in one-to-one chat, group ID in group chat, and chat room ID in room chat.
        NSString* conversationId = @"remoteUserId";
        AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:conversationId
                                                              body:textMessageBody
                                                               ext:messageExt];
        // Set `message.chatType` to `AgoraChatTypeChat` for one-to-one chat, `AgoraChatTypeGroupChat` for group chat, and `AgoraChatTypeChatRoom` for room chat.
        // The default value is `AgoraChatTypeChat`.
        message.chatType = AgoraChatTypeChat;
        // Send the message.
        [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                       progress:nil
                                                     completion:nil];
    ```

    You can set the priority of chat room messages.

    ### Send a message to online users only [#send-a-message-to-online-users-only-1]

    Agora Chat supports delivering messages only to users that are currently online. An example of using this function is displaying the votes in a group voting: Only users that are online need to see the changes in real time, while the others only need the final result.

    All types of messages in one-to-one chats and chat groups support this function. Compared to an ordinary message, a message that is delivered only to online users has the following differences:

    * Does not support offline storage: If the recipient is offline when sending a message, the message cannot be received, even after logging in again. For ordinary messages, when the recipient is online, the message reminder is received in real time; when the recipient is offline, the offline push message is sent in real time. When the recipient is online again, the Chat server actively pushes the offline message to the client.
    * Support local storage: After a message is successfully sent, it is added to the database.
    * Roaming storage is not supported by default: Sent messages are not stored on the Chat message server by default, so users cannot obtain the messages on other devices. If you need to activate roaming storage of online messages, contact [support@agora.io](mailto\:support@agora.io).

    To deliver messages only to online users, set `ChatMessage#deliverOnlineOnly` to `true` when sending the message. The following is an example of sending a text message to only online users:

    ```objc
    // Call initWithText to create a text message. `content` is the content of the text message.
    AgoraTextMessageBody *textMessageBody = [[AgoraTextMessageBody alloc] initWithText:content];
    // Message receiver: Single chat is the ID of the peer user, group chat is the group ID.
    NSString* conversationId = @"remoteUserId";
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:conversationId
                                                          body:textMessageBody
                                                                   ext:messageExt];
    // Conversation type: Single chat is `AgoraChatTypeChat`, group chat is `AgoraChatTypeGroupChat`.
    message.chatType = AgoraChatTypeChat;
    // Whether the message is only delivered to online users. (Default) `NO`: Delivered regardless of whether the user is online or not; `YES`: Only delivered to online users. If the user is offline, the message will not be delivered.
    message.deliverOnlineOnly = YES；
    // Send a message.
    [[AgoraClient sharedClient].chatManager sendMessage:message
                                            progress:nil
                                          completion:nil];
    ```

    ### Set message priority [#set-message-priority-1]

    In high-concurrency use-cases, you can set a certain message type or messages from a chat room member as high, normal, or low priority.
    In this case, low-priority messages are dropped first to reserve resources for the high-priority ones, for example, gifts and announcements, when the server is overloaded.
    This ensures that the high-priority messages can be dealt with first when loads of messages are being sent in high concurrency or high frequency.
    Note that this feature can increase the delivery reliability of high-priority messages, but cannot guarantee the deliveries.
    Even high-priorities messages can be dropped when the server load goes too high.

    You can set the priority for all types of messages in the chat room.

    ```objc
    AgoraChatTextMessageBody* textBody = [[AgoraChatTextMessageBody alloc] initWithText:@"Hi"];
        AgoraChatMessage* message = [[AgoraChatMessage alloc] initWithConversationID:@"roomId" body:textBody ext:nil];
        message.chatType = AgoraChatTypeChatRoom;
        // Set the message priority. The default value is `Normal`, indicating the normal priority.
        message.priority = AgoraChatRoomMessagePriorityHigh;
    [AgoraChatClient.sharedClient.chatManager sendMessage:message progress:nil completion:nil];
    ```

    ### Receive a message [#receive-a-message-1]

    You can use `AgoraChatManagerDelegate` to listen for message events. You can add multiple delegates to listen for multiple events. When you no longer listen for an event, ensure that you remove the listener.

    When a message arrives, the recipient receives an `messagesDidReceive` callback. Each callback contains one or more messages. You can traverse the message list, and parse and render these messages in this callback and render these messages.

    ```objc
    // Adds the delegate.
    [[AgoraChatClient sharedClient].chatManager addDelegate:self delegateQueue:nil];
    // Occurs when the message is received.
    - (void)messagesDidReceive:(NSArray *)aMessages
    {
      // Traverse the message list
      for (AgoraChatMessage *message in aMessages) {
        // Parse the message and display it on the UI
      }
    }
    // Removes the delegate.
    - (void)dealloc
    {
      [[AgoraChatClient sharedClient].chatManager removeDelegate:self];
    }
    ```

    ### Recall a message [#recall-a-message-1]

    After a message is sent, you can recall it. The `recallMessageWithMessageId` method recalls a message that is saved both locally and on the server, whether it is a historical message, offline message or a roaming message on the server, or a message in the memory or local database of the message sender or recipient.

    The default time limit for recalling a message is two minutes. You can extend this time frame to up to 7 days in [Agora Console](https://console.agora.io/v2). To do so, select a project that enables Agora Chat, then click **Configure** > **Features** > **Message recall**.

    The following permission rules apply to message recall:

    * In one-to-one chats, only the message sender can recall a message they sent. If the message has expired, the recall fails.
    * In chat groups and chat rooms, regular members can recall only the messages they sent, and the recall fails if the message has expired. Since SDK v1.4.0, the chat group owner, chat room owner, and admins can recall messages sent by other members, even after those messages have expired.

    ![message-recall](https://assets-docs.agora.io/images/im/message-recall.png)

    <CalloutContainer type="info">
      <CalloutDescription>
        1. Except CMD messages, you can recall all types of message. 2. If an attachment message, like an image, voice, video, or file message, is recalled, the attachment of the message is also deleted.
      </CalloutDescription>
    </CalloutContainer>

    ```objc
    [[AgoraChatClient sharedClient].chatManager recallMessageWithMessageId:@"messageId" completion:^(AgoraChatError *aError) {
            if (!aError) {
                   NSLog(@"Recalling message succeeds");
               } else {
                   NSLog(@"Recalling message fails — %@", aError.errorDescription);
               }
        }];
    ```

    You can also use `messagesDidRecall` to listen for the message recall state:

    ```objc
    /**
     * Occurs when a received message is recalled.
     */
    - (void)messagesInfoDidRecall:(NSArray * _Nonnull)aRecallMessagesInfo;
    ```

    ### Send and receive an attachment message [#send-and-receive-an-attachment-message-1]

    Voice, image, video, and file messages are essentially attachment messages. This section introduces how to send these types of messages.

    #### Send and receive a voice message [#send-and-receive-a-voice-message-1]

    Before sending a voice message, you should implement audio recording on the app level, which provides the URI and duration of the recorded audio file.

    Refer to the following code example to create and send a voice message:

    ```objc
    // Set localPath as the local path of the voice file and displayName the display name of the attachment.
    AgoraChatVoiceMessageBody *body = [[AgoraChatVoiceMessageBody alloc] initWithLocalPath:localPath
                                                 displayName:displayName];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:nil];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    When the recipient receives the message, refer to the following code example to get the audio file:

    ```objc
    AgoraChatVoiceMessageBody *voiceBody = (AgoraChatVoiceMessageBody *)message.body;
    // Retrieves the path of the audio file on the server.
    NSString *voiceRemotePath = voiceBody.remotePath;
    // Retrieves the path of the audio file on the local device.
    NSString *voiceLocalPath = voiceBody.localPath;
    ```

    #### Send and receive an image message [#send-and-receive-an-image-message-1]

    By default, the SDK compresses the image file before sending it. To send the originial file, you can set `original` as `true`.

    Refer to the following code example to create and send an image message:

    ```objc
    // Set imageData as the path of the image file on the local device, and displayName as the display name of the file.
    AgoraChatImageMessageBody *body = [[AgoraChatImageMessageBody alloc] initWithData:imageData
                                                        displayName:displayName];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    When the recipient receives the message, refer to the following code example to get the thumbnail and attachment file of the image message:

    ```objc
    AgoraChatImageMessageBody *body = (AgoraChatImageMessageBody *)message.body;
    // Retrieves the path of the image file on the server.
    NSString *remotePath = body.remotePath;
    // Retrieves the path of the image thumbnail from the server.
    NSString *thumbnailPath = body.thumbnailRemotePath;
    // Retrieves the path of the image file on the local device.
    NSString *localPath = body.localPath;
    // Retrieves the path of the image thumbnail on the local device.
    NSString *thumbnailLocalPath = body.thumbnailLocalPath;
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        If `[AgoraChatClient sharedClient].options.isAutoDownloadThumbnail` is set as `YES` on the recipient's client, the SDK automatically downloads the thumbnail after receiving the message. If not, you need to call `[[AgoraChatClient sharedClient].chatManager downloadMessageThumbnail:message progress:nil completion:nil];` to download the thumbnail and get the path from the `thumbnailLocalPath` member in `messageBody`.
      </CalloutDescription>
    </CalloutContainer>

    #### Send and receive a GIF image message [#send-and-receive-a-gif-image-message-1]

    Since SDK v1.4.0, you can send and receive GIF image messages. A GIF image message is a subtype of image message that is **not compressed** when sent. Thumbnail generation and download are the same as for a regular image message.

    To send a GIF image message, set the `isGif` property of the `AgoraChatImageMessageBody` to `YES`, or construct the body with `initWithGifFilePath:displayName:`:

    ```objc
    // Option 1: set isGif on an image message body created from data.
    AgoraChatImageMessageBody *body = [[AgoraChatImageMessageBody alloc] initWithData:imageData displayName:displayName];
    body.isGif = YES;

    // Option 2: create the body from a local GIF file path.
    AgoraChatImageMessageBody *body = [[AgoraChatImageMessageBody alloc] initWithGifFilePath:@"localGifFilePath" displayName:displayName];

    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername from:fromChatUsername to:toChatUsername body:body ext:messageExt];
    [[AgoraChatClient sharedClient].chatManager sendMessage:message progress:nil completion:nil];
    ```

    As with a regular message, the recipient receives the `messagesDidReceive` callback. After confirming that the message is an image message, read the `isGif` property of the message body. If it returns `YES`, the message is a GIF image message.

    ```objc
    - (void)messagesDidReceive:(NSArray<AgoraChatMessage *> *)aMessages {
        for (AgoraChatMessage *message in aMessages) {
            if (message.body.type == AgoraChatMessageBodyTypeImage) {
                AgoraChatImageMessageBody *body = (AgoraChatImageMessageBody *)message.body;
                if (body.isGif) {
                    // Handle the GIF image message according to your business logic, for example, download and display it.
                }
            }
        }
    }
    ```

    #### Send and receive a video message [#send-and-receive-a-video-message-1]

    Before sending a video message, you should implement video capturing on the app level, which provides the duration of the captured video file.

    Refer to the following code example to create and send a video message:

    ```objc
    // Set localPath as the path of the video file on the local device and displayName the display name of the video file.
    AgoraChatVideoMessageBody *body = [[AgoraChatVideoMessageBody alloc] initWithLocalPath:localPath displayName:@"displayName"];
    // The duration of the video file
    body.duration = duration;
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    By default, when the recipient receives the message, the SDK downloads the thumbnail of the video message.

    If you do not want the SDK to automatically download the video thumbnail, set `[AgoraChatClient sharedClient].options.isAutoDownloadThumbnail` as `NO`, and to download the thumbnail, you need to call `[[AgoraChatClient sharedClient].chatManager downloadMessageThumbnail:message progress:nil completion:nil]`, and get the path of the thumbnail from the `thumbnailLocalPath` member in `messageBody`.

    To download the actual video file, call `[[AgoraChatClient sharedClient].chatManager downloadMessageAttachment:progress:completion:]`, and get the path of the video file from the `getLocalUri` member in `messageBody`.

    ```objc
    AgoraChatVideoMessageBody *body = (AgoraChatVideoMessageBody *)message.body;
    // Retrieves the path of the video file from the server.
    NSString *remotePath = body.remotePath;
    // Retrieves the thumbnail of the video file from the server.
    NSString *thumbnailPath = body.thumbnailRemotePath;
    // Retrieves the path of the video file on the local device.
    NSString *localPath = body.localPath;
    // Retrieves the thumbnail of the video file on the local device.
    NSString *thumbnailLocalPath = body.thumbnailLocalPath;
    ```

    #### Send and receive a file message [#send-and-receive-a-file-message-1]

    Refer to the following code example to create, send, and receive a file message:

    ```objc
    // Set fileData as the path of the file on the local device, and fileName the display name of the attachment file.
    AgoraChatFileMessageBody *body = [AgoraChatFileMessageBody  initWithData:fileData
                                                         displayName:fileName];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    While sending a file message, refer to the following sample code to get the progress for uploading the attachment file:

    ```objc
    [[AgoraChatClient sharedClient].chatManager sendMessage:message progress:^(int progress) {
        //progress indicates the progress of uploading the attachment
    } completion:^(AgoraChatMessage *message, AgoraChatError *error) {
        //error indicates the result of sending the message, and message indicates the sent message
    }];
    ```

    When the recipient receives the message, refer to the following code example to get the attachment file:

    ```objc
    AgoraChatFileMessageBody *body = (AgoraChatFileMessageBody *)message.body;
    // Retrieves the path of the attachment file from the server.
    NSString *remotePath = body.remotePath;
    // Retrieves the path of the attachment file on the local device.
    NSString *localPath = body.localPath;
    ```

    ### Send a location message [#send-a-location-message-1]

    To send and receive a location message, you need to integrate a third-party map service provider. When sending a location message, you get the longitude and latitude information of the location from the map service provider; when receiving a location message, you extract the received longitude and latitude information and display the location on the third-party map.

    ```objc
    // Sets the latitude and longitude information of the location.
    AgoraChatLocationMessageBody *body = [[AgoraChatLocationMessageBody alloc] initWithLatitude:latitude longitude:longitude address:aAddress];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    ### Send and receive a CMD message [#send-and-receive-a-cmd-message-1]

    CMD messages are command messages that instruct a specified user to take a certain action. The recipient deals with the command messages themselves.

    <CalloutContainer type="info">
      <CalloutDescription>
        CMD messages are not stored in the local database. Actions beginning with `em_` and `easemob::` are internal fields. Do not use them.
      </CalloutDescription>
    </CalloutContainer>

    ```objc
    // Use action to customize the CMD message
    AgoraChatCmdMessageBody *body = [[AgoraChatCmdMessageBody alloc] initWithAction:action];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    ```

    To notify the recipient that a CMD message is received, use a separate delegate so that users can deal with the message differently.

    ```objc
    // Occurs when the CMD message is received.
    - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages{
      for (AgoraChatMessage *message in aCmdMessages) {
            AgoraChatCmdMessageBody *body = (AgoraChatCmdMessageBody *)message.body;
            // Parse the message body
        }
    }
    ```

    ### Send a customized message [#send-a-customized-message-1]

    The following code example shows how to create and send a customized message:

    ```objc
    // Set event as the custom message event, for example "userCard".
    // Set ext as the extension field of the event, for example as uid, nickname, and avatar.
    AgoraChatCustomMessageBody* body = [[AgoraChatCustomMessageBody alloc] initWithEvent:@"userCard" ext:@{@"uid":aUid ,@"nickname":aNickName,@"avatar":aUrl}];
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                          from:fromChatUsername
                                                             to:toChatUsername
                                                           body:body
                                                           ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Set the chat type as Group chat. You can also set it as chat (one-to-one chat) or chat room.
    // message.chatType = AgoraChatTypeGroupChat;
    // Sends the message.
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                               progress:nil
                             completion:nil];

    ```

    ### Use message extensions [#use-message-extensions-1]

    If the message types listed above do not meet your requirements, you can use message extensions to add attributes to the message. This can be applied in more complicated messaging use-cases.

    ```objc
    AgoraChatTextMessageBody *textMessageBody = [[AgoraChatTextMessageBody alloc] initWithText:content];
    // Adds the message extension.
    NSDictionary *messageExt = @{@"attribute":@"value"};
    AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:toChatUsername
                                                           from:fromChatUsername
                                                              to:toChatUsername
                                                            body:textMessageBody
                                                            ext:messageExt];
    message.chatType = AgoraChatTypeChat;
    // Sends the message
    [[AgoraChatClient sharedClient].chatManager sendMessage:message
                                                   progress:nil
                                                 completion:nil];
    // Retrieves the extension when receiving the message
    - (void)messagesDidReceive:(NSArray *)aMessages
    {
      // Traverse the message list
      for (AgoraChatMessage *message in aMessages) {
         //value indicates the value of the attribute field in the message extension
         NSString *value = [message.ext objectForKey:@"attribute"];
      }
    }
    ```

    ### Forward a single message [#forward-a-single-message-1]

    You can call the `AgoraChatMessage` constructor method to create a message that is exactly the same as the original message by passing in the message body and extended fields of the original message (if any), and then call the `ChatManager#sendMessage` method to forward the message.

    You can forward all types of messages in individual chats, chat groups, and chat rooms. For messages with attachments, there's no need to re-upload the attachments during forwarding.

    However, if the original message expires (for example, deleted from the server due to storage limitations), the recipient can view the attachment address after forwarding but won't be able to download the attachment.

    To forward a single message, refer to the following code:

    ```objc
    // messageId: The ID of the message to be forwarded.
    if let message = AgoraChatClient.shared().chatManager?.getMessageWithMessageId("messageId") {
        // Create a new message with the body and extension fields of the original message.
        let newMessage = AgoraChatMessage(conversationID: "conversationId", body: message.body, ext: message.ext)
        AgoraChatClient.shared().chatManager?.send(newMessage, progress: nil, completion: { messageResult, err in
            if err == nil {
                // Succeeded in forwarding the message.
            }
        })
    }
    ```

    ### Forward multiple messages [#forward-multiple-messages-1]

    Supported types for forwarded messages include text, images, audio & video files, attachment, and custom messages. A user can create a combined message with a list of original messages and send it. When receiving a combined message, the recipient can select it and other messages to create a new layered combined message. A combined message can contain up to 10 layers of messages, with at most 300 messages at each layer.

    To forward and receive combined messages, refer to the following code:

    1. Create a combined message using multiple message IDs:

       ```objc
       AgoraChatCombineMessageBody *body = [[AgoraChatCombineMessageBody alloc] initWithTitle:@"Chat History" summary:@"summary" compatibleText:@"The version is low and unable to display the content." messageIdList:@[@"messageId1",@"messageId2"]];
           AgoraChatMessage *message = [[AgoraChatMessage alloc] initWithConversationID:@"conversationId" body:body ext:nil];
           [AgoraChatClient.sharedClient.chatManager sendMessage:message progress:nil completion:^(AgoraChatMessage *aMessage, AgoraChatError *aError) {
       }];
       ```

    2. Download and parse combined messages:

       ```objc
       - (void)messagesDidReceive:(NSArray *)aMessages
       {
           for(AgoraChatMessage* msg in aMessages) {
               if (msg.body.type == AgoraChatMessageBodyTypeCombine) {
                   [AgoraChatClient.sharedClient.chatManager downloadAndParseCombineMessage:msg completion:^(NSArray * _Nullable messages, AgoraChatError * _Nullable error) {

                   }];
               }
           }
       }
       ```

    ### Modify sent messages [#modify-sent-messages-1]

    For messages that have been sent successfully, the SDK supports modifying the message content.

    * Before SDK v1.4.0, you could only modify text messages sent in one-to-one chats and chat groups.
    * Since SDK v1.4.0, you can modify various message types sent in one-to-one chats, chat groups, and chat rooms:
      * Text and custom messages: modify both the message body and the extension fields (`ext`).
      * File, video, voice, image, location, and combined messages: modify the extension fields (`ext`) only.
      * Command messages: modification is not supported.

    There is no time limit for modifying a message, that is, it can be modified as long as the message is still stored on the server. After the message is modified, the message life cycle, that is, its storage time on the server, is recalculated. For example, a message can be stored on the server for 180 days, and the user modifies it on the 30th day after the message was sent. Instead of remaining 150 days, the message can be now stored on the server for 180 days after successful modification.

    In the modified message, the message ID remains unchanged. The message body, the extension fields, or both are edited and the following items are added:

    * The operator ID of the user performing the action.
    * The operation time that indicates when the message was edited.
    * The number of times a message is edited (up to 10 times).

    For the edited message, other information included in the message, such as the message sender and recipient, remains unchanged.

    To modify a sent message, refer to the following code:

    1. Call `modifyMessage` with the message ID, the new message body, and the new extension fields:
       ```objc
       // Text message: you can modify both the message body and the extension fields.
       AgoraChatTextMessageBody *newBody = [[AgoraChatTextMessageBody alloc] initWithText:@"new content"];
       NSDictionary *newExt = @{@"newKey": @"newValue"};
       // newBody and newExt cannot both be nil.
       [AgoraChatClient.sharedClient.chatManager modifyMessage:@"messageId" body:newBody ext:newExt completion:^(AgoraChatError * _Nullable error, AgoraChatMessage * _Nullable message) {

       }];

       // Custom message: you can modify both the message body and the extension fields.
       AgoraChatCustomMessageBody *newCustomBody = [[AgoraChatCustomMessageBody alloc] initWithEvent:@"event" customExt:@{@"key": @"value"}];
       [AgoraChatClient.sharedClient.chatManager modifyMessage:@"messageId" body:newCustomBody ext:@{@"newKey": @"newValue"} completion:^(AgoraChatError * _Nullable error, AgoraChatMessage * _Nullable message) {

       }];

       // File, video, voice, image, location, or combined message: you can modify the extension fields only. Pass nil as the message body.
       [AgoraChatClient.sharedClient.chatManager modifyMessage:@"messageId" body:nil ext:@{@"newKey": @"newValue"} completion:^(AgoraChatError * _Nullable error, AgoraChatMessage * _Nullable message) {

       }];
       ```
    2. Receive notification of messages modified by other users:
       ```objc
       - (void)onMessageContentChanged:(AgoraChatMessage *)message operatorId:(NSString *)operatorId operationTime:(NSUInteger)operationTime {

       }
       ```

    For further details see [Sent message modification limitations](../../limitations#sent-message-modification-limitations).

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

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

    * [Manage local messages](./manage-messages)

    * [Retrieve conversations and messages from the server](./retrieve-messages)

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

    
  
      
  
      
  
      
  
      
  
      
  
