diff --git a/src/pages/docs/chat/rooms/messages.mdx b/src/pages/docs/chat/rooms/messages.mdx
index 409430dfef..d723f08c91 100644
--- a/src/pages/docs/chat/rooms/messages.mdx
+++ b/src/pages/docs/chat/rooms/messages.mdx
@@ -228,6 +228,98 @@ fun MyComponent(room: Room) {
```
+### Handle self-published messages
+
+When you send a message using `send()`, the server echoes it back to all subscribers in the room, including the sender. If your application adds the message to the UI immediately before/after calling `send()` and also appends it when received via `subscribe()`, the message will appear twice. There are two approaches to handle this.
+
+#### Wait for the subscriber
+
+The recommended approach is to not add the message to the UI immediately before/after calling `send()`. Instead, only append messages to the UI inside the `subscribe()` listener. Since the server echoes every sent message back to the sender as a subscriber event, the message will still appear in the UI when it arrives through the subscription. This eliminates the duplication problem entirely and requires no deduplication logic in the subscriber.
+
+This approach has the advantage that the message list is only written to from a single place — the subscriber. This means you don't need a concurrent data structure or additional synchronization to protect the list from simultaneous writes. The tradeoff is that the sent message must complete a round trip to the server before appearing in the UI. While Ably's realtime delivery is always near-instantaneous, this may introduce a slight delay rarely in poor network conditions.
+
+#### Deduplicate with optimistic UI
+
+If your application adds the message to the UI immediately before/after calling `send()` for a more responsive experience, you need to add a safety check in the subscriber to avoid duplicates. Validate the incoming message `serial` and `version` against existing messages.
+
+Because the message list is written to from two places — once before/after `send()` and again inside the subscriber — you must use a concurrent or thread-safe data structure to handle simultaneous writes safely. Additionally, each incoming message requires a lookup through the existing message list to check for duplicates, which adds CPU overhead that grows with the size of the list:
+
+
+```javascript
+const {unsubscribe} = room.messages.subscribe((event) => {
+ // Early return if a message with the same serial and version.serial already exists
+ const existingMessage = myMessageList.find(msg => msg.serial === event.message.serial);
+ if (existingMessage && existingMessage.version.serial === event.message.version.serial) {
+ return;
+ }
+ // Process the message
+});
+```
+
+```react
+import { useMessages } from '@ably/chat/react';
+
+const MyComponent = () => {
+ useMessages({
+ listener: (event) => {
+ // Early return if a message with the same serial and version.serial already exists
+ const existingMessage = myMessageList.find(msg => msg.serial === event.message.serial);
+ if (existingMessage && existingMessage.version.serial === event.message.version.serial) {
+ return;
+ }
+ // Process the message
+ },
+ });
+
+ return ...
;
+};
+```
+
+```swift
+let messagesList: [Message]
+let messagesSubscription = try await room.messages.subscribe()
+for await message in messagesSubscription {
+ // Early return if a message with the same serial and version already exists
+ let existingMessage = messagesList.first(where: { $0.serial == message.serial })
+ if existingMessage != nil && existingMessage?.version.serial == message.version.serial {
+ continue
+ }
+ // Process the message
+}
+```
+
+```kotlin
+val myMessageList: List
+val subscription = room.messages.subscribe { event: ChatMessageEvent ->
+ // Early return if a message with the same serial and version.serial already exists
+ val existingMessage = myMessageList.find { it.serial == event.message.serial }
+ if (existingMessage != null && existingMessage.version.serial == event.message.version.serial) return@subscribe
+ // Process the message
+}
+```
+
+```android
+import androidx.compose.runtime.*
+import com.ably.chat.Message
+import com.ably.chat.Room
+import com.ably.chat.asFlow
+
+@Composable
+fun MyComponent(room: Room) {
+ var myMessageList by remember { mutableStateOf>(emptyList()) }
+
+ LaunchedEffect(room) {
+ room.messages.asFlow().collect { event ->
+ // Early return if a message with the same serial and version.serial already exists
+ val existingMessage = myMessageList.find { it.serial == event.message.serial }
+ if (existingMessage != null && existingMessage.version.serial == event.message.version.serial) return@collect
+ // Process the message
+ }
+ }
+}
+```
+
+
## Get a single message
@@ -446,7 +538,7 @@ for await message in messagesSubscription {
```
```kotlin
-val myMessageList: List
+val myMessageList: List
val messagesSubscription = room.messages.subscribe { event ->
when (event.type) {
ChatMessageEventType.Created -> println("Received message: ${event.message}")
@@ -700,7 +792,7 @@ for await message in messagesSubscription {
```
```kotlin
-val myMessageList: List
+val myMessageList: List
val messagesSubscription = room.messages.subscribe { event ->
when (event.type) {
ChatMessageEventType.Created -> println("Received message: ${event.message}")