diff --git a/content/partials/types/_message.textile b/content/partials/types/_message.textile
index d392589dea..00f65e50bb 100644
--- a/content/partials/types/_message.textile
+++ b/content/partials/types/_message.textile
@@ -64,18 +64,23 @@ blang[jsall].
h6(#action).
default: action
- The action type of the message, one of the "@MessageAction@":/docs/api/realtime-sdk/types#message-action enum values. __Type: @int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }@__
+ The action type of the message, one of the "@MessageAction@":#message-action enum values. __Type: @int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }@__
h6(#serial).
default: serial
- A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in "channel rules":/docs/channels#rules . __Type: @String@__
+ A server-assigned identifier that will be the same in all future updates of this message. It can be used to add "annotations":/docs/messages/annotations to a message or to "update or delete":/docs/messages/updates-deletes it. Serial will only be set if you enable annotations, updates, and deletes in "channel rules":/docs/channels#rules . __Type: @String@__
h6(#annotations).
default: annotations
An object containing information about annotations that have been made to the object. __Type: "@MessageAnnotations@":/docs/api/realtime-sdk/types#message-annotations__
+ h6(#version).
+ default: version
+
+ An object containing version metadata for messages that have been updated or deleted. See "updating and deleting messages":/docs/messages/updates-deletes for more information. __Type: "@MessageVersion@":#message-version__
+
h3(constructors).
default: Message constructors
@@ -112,3 +117,15 @@ h4. Parameters
h4. Returns
An @Array@ of "@Message@":/docs/api/realtime-sdk/types#message objects
+
+h3(#message-version).
+ default: MessageVersion
+
+h4. Properties
+
+|_. Property |_. Description |_. Type |
+| serial | An Ably-generated ID that uniquely identifies this version of the message. Can be compared lexicographically to determine version ordering. For an original message with an action of @message.create@, this will be equal to the top-level @serial@. | @String@ |
+| timestamp | The time this version was created (when the update or delete operation was performed). For an original message, this will be equal to the top-level @timestamp@. | @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@ |
+| clientId | The client identifier of the user who performed the update or delete operation. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) |
+| description | Optional description provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) |
+| metadata | Optional metadata provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @Object@ (optional) |
diff --git a/content/partials/types/_message_action.textile b/content/partials/types/_message_action.textile
index b525ba9066..ef6d69b2ae 100644
--- a/content/partials/types/_message_action.textile
+++ b/content/partials/types/_message_action.textile
@@ -1,11 +1,61 @@
-@Message@ @action@ is a String representing the action type of the message.
+blang[default].
+ @MessageAction@ is an enum representing the action type of the message.
-```[javascript]
- const MessageActions = [
+ ```
+ enum MessageAction {
+ MESSAGE_CREATE, // 0
+ MESSAGE_UPDATE, // 1
+ MESSAGE_DELETE, // 2
+ META, // 3
+ MESSAGE_SUMMARY // 4
+ }
+ ```
+
+blang[jsall].
+ @Message@ @action@ is a String representing the action type of the message.
+
+ ```[javascript]
+ var MessageActions = [
'message.create',
'message.update',
'message.delete',
'meta',
'message.summary'
- ]
-```
+ ]
+ ```
+
+blang[java].
+ @io.ably.lib.types.Message.Action@ is an enum representing the action type of the message.
+
+ ```[java]
+ public enum Action {
+ MESSAGE_CREATE, // 0
+ MESSAGE_UPDATE, // 1
+ MESSAGE_DELETE, // 2
+ META, // 3
+ MESSAGE_SUMMARY // 4
+ }
+ ```
+
+blang[objc,swift].
+ @ARTMessageAction@ is an enum representing the action type of the message.
+
+ ```[objc]
+ typedef NS_ENUM(NSUInteger, ARTMessageAction) {
+ ARTMessageActionCreate,
+ ARTMessageActionUpdate,
+ ARTMessageActionDelete,
+ ARTMessageActionMeta,
+ ARTMessageActionMessageSummary
+ };
+ ```
+
+ ```[swift]
+ enum ARTMessageAction : UInt {
+ case Create
+ case Update
+ case Delete
+ case Meta
+ case Summary
+ }
+ ```
diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts
index 3d949f045d..b29bc548d4 100644
--- a/src/data/nav/pubsub.ts
+++ b/src/data/nav/pubsub.ts
@@ -199,6 +199,10 @@ export default {
name: 'Message annotations',
link: '/docs/messages/annotations',
},
+ {
+ name: 'Updates and deletes',
+ link: '/docs/messages/updates-deletes',
+ },
],
},
{
diff --git a/src/pages/docs/api/realtime-sdk/channels.mdx b/src/pages/docs/api/realtime-sdk/channels.mdx
index 4e3f16472b..d95c25bb2a 100644
--- a/src/pages/docs/api/realtime-sdk/channels.mdx
+++ b/src/pages/docs/api/realtime-sdk/channels.mdx
@@ -816,6 +816,85 @@ Failure to retrieve the message history will trigger the `errback` callbacks of
+
+
+#### getMessage
+
+`getMessage(serialOrMessage: string | Message): Promise`
+
+Retrieves the latest version of a specific message by its serial identifier. Requires the **history** [capability](/docs/auth/capabilities).
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| serialOrMessage | Either the serial identifier string of the message to retrieve, or a [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | `string` or [`Message`](/docs/api/realtime-sdk/messages) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object representing the latest version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### updateMessage
+
+`updateMessage(message: Message, operation?: MessageOperation, params?: Record): Promise`
+
+Publishes an update to an existing message with patch semantics. Non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field and the fields to update | [`Message`](/docs/api/realtime-sdk/messages) |
+| operation | An optional `MessageOperation` object containing metadata about the update operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object containing the updated message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### deleteMessage
+
+`deleteMessage(message: Message, operation?: MessageOperation, params?: Record): Promise`
+
+Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses patch semantics: non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | [`Message`](/docs/api/realtime-sdk/messages) |
+| operation | An optional `MessageOperation` object containing metadata about the delete operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object containing the deleted message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### getMessageVersions
+
+`getMessageVersions(serialOrMessage: string | Message, params?: Record): Promise>`
+
+Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. Requires the **history** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| serialOrMessage | Either the serial identifier string of the message whose versions are to be retrieved, or a [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | `string` or [`Message`](/docs/api/realtime-sdk/messages) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`PaginatedResult`](#paginated-result) object containing an array of [`Message`](/docs/api/realtime-sdk/messages) objects representing all versions of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+
+
#### setOptions
@@ -1847,22 +1926,23 @@ Timestamp when the message was first received by the Ably, as
+
-#### action
+### action
-The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values.
-**Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`**
+The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
-#### serial
+### serial
-A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in [channel rules](/docs/channels#rules).
-**Type: `String`**
+A server-assigned identifier that will be the same in all future updates of this message. It can be used to add [annotations](/docs/messages/annotations) to a message or to [update or delete](/docs/messages/updates-deletes) it. Serial will only be set if you enable annotations, updates, and deletes in [channel rules](/docs/channels#rules). _Type: `String`_
+
+### annotations
+
+An object containing information about annotations that have been made to the object. _Type: [`MessageAnnotations`](/docs/api/realtime-sdk/types#message-annotations)_
-#### annotations
+### version
-An object containing information about annotations that have been made to the object.
-**Type: [`MessageAnnotations`](/docs/api/realtime-sdk/types#message-annotations)**
+An object containing version metadata for messages that have been updated or deleted. See [updating and deleting messages](/docs/messages/updates-deletes) for more information. _Type: [`MessageVersion`](#message-version)_
diff --git a/src/pages/docs/api/realtime-sdk/messages.mdx b/src/pages/docs/api/realtime-sdk/messages.mdx
index 13aaa7a243..7118cd20df 100644
--- a/src/pages/docs/api/realtime-sdk/messages.mdx
+++ b/src/pages/docs/api/realtime-sdk/messages.mdx
@@ -48,20 +48,24 @@ Timestamp when the message was first received by the Ably, as
+
-### action
+### action
-The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
+The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
-### serial
+### serial
-A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in [channel rules](/docs/channels#rules). _Type: `String`_
+A server-assigned identifier that will be the same in all future updates of this message. It can be used to add [annotations](/docs/messages/annotations) to a message or to [update or delete](/docs/messages/updates-deletes) it. Serial will only be set if you enable annotations, updates, and deletes in [channel rules](/docs/channels#rules). _Type: `String`_
-### annotations
+### annotations
An object containing information about annotations that have been made to the object. _Type: [`MessageAnnotations`](/docs/api/realtime-sdk/types#message-annotations)_
+### version
+
+An object containing version metadata for messages that have been updated or deleted. See [updating and deleting messages](/docs/messages/updates-deletes) for more information. _Type: [`MessageVersion`](#message-version)_
+
### Message constructors
@@ -107,3 +111,15 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects
| Property | Description | Type |
|----------|-------------|------|
| summary | An object whose keys are annotation types, and the values are aggregated summaries for that annotation type | `Record` |
+
+## MessageVersion
+
+#### PropertiesMembersAttributes
+
+| Property | Description | Type |
+|----------|-------------|------|
+| serial | An Ably-generated ID that uniquely identifies this version of the message. Can be compared lexicographically to determine version ordering. For an original message with an action of `message.create`, this will be equal to the top-level `serial`. | `String` |
+| timestamp | The time this version was created (when the update or delete operation was performed). For an original message, this will be equal to the top-level `timestamp`. | `Integer``Long Integer``DateTimeOffset``Time``NSDate` |
+| clientId | The client identifier of the user who performed the update or delete operation. Only present for `message.update` and `message.delete` actions. | `String` (optional) |
+| description | Optional description provided when the update or delete was performed. Only present for `message.update` and `message.delete` actions. | `String` (optional) |
+| metadata | Optional metadata provided when the update or delete was performed. Only present for `message.update` and `message.delete` actions. | `Object` (optional) |
diff --git a/src/pages/docs/api/realtime-sdk/types.mdx b/src/pages/docs/api/realtime-sdk/types.mdx
index 1f50d126d6..d588159d4e 100644
--- a/src/pages/docs/api/realtime-sdk/types.mdx
+++ b/src/pages/docs/api/realtime-sdk/types.mdx
@@ -480,9 +480,11 @@ A static factory method to create an array of [`Messages`](/docs/api/realtime-sd
An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects
-
+
+
+### Message actionARTMessageActionio.ably.lib.types.Message.Action
-### Message action
+
`Message` `action` is a String representing the action type of the message.
@@ -515,6 +517,58 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects
```
+
+
+
+`io.ably.lib.types.Message.Action` is an enum representing the action type of the message.
+
+
+```java
+ public enum Action {
+ MESSAGE_CREATE, // 0
+ MESSAGE_UPDATE, // 1
+ MESSAGE_DELETE, // 2
+ META, // 3
+ MESSAGE_SUMMARY // 4
+ }
+```
+
+
+
+
+
+`ARTMessageAction` is an enum representing the action type of the message.
+
+
+
+
+```objc
+ typedef NS_ENUM(NSUInteger, ARTMessageAction) {
+ ARTMessageActionCreate,
+ ARTMessageActionUpdate,
+ ARTMessageActionDelete,
+ ARTMessageActionMeta,
+ ARTMessageActionMessageSummary
+ };
+```
+
+
+
+
+
+
+```swift
+ enum ARTMessageAction : UInt {
+ case Create
+ case Update
+ case Delete
+ case Meta
+ case Summary
+ }
+```
+
+
+
diff --git a/src/pages/docs/api/rest-api.mdx b/src/pages/docs/api/rest-api.mdx
index aa4f7527a3..a4b5b8d14e 100644
--- a/src/pages/docs/api/rest-api.mdx
+++ b/src/pages/docs/api/rest-api.mdx
@@ -12,6 +12,10 @@ jump_to:
Channel API:
- publish
- message history#message-history
+ - get message#get-message
+ - update message#update-message
+ - delete message#delete-message
+ - message versions#message-versions
- presence
- presence history
Push API:
@@ -595,6 +599,276 @@ In each case a successful result is a [paginated response](#pagination) with an
```
+### Get latest version of a message
+
+#### GET main.realtime.ably.net/channels/\/messages/\
+
+Retrieve the latest version of a specific message by its serial identifier.
+
+Example request:
+
+
+```shell
+curl https://main.realtime.ably.net/channels/rest-example/messages/01826232498871-001@abcdefghij:001 \
+ -u "{{API_KEY}}"
+```
+
+
+##### Options
+
+| Property | Value |
+|----------|-------|
+| Accept | `application/json` or `application/x-msgpack` |
+| Auth required | yes ([basic](basic-authentication) or [token](token-authentication) with `history` capability) |
+
+##### Returns
+
+A successful request returns a single `Message` object containing the latest version of the message.
+
+See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum.
+
+
+```json
+{
+ serial: ,
+ name: ,
+ data: ,
+ timestamp: ,
+ clientId: ,
+ action: ,
+ version: {
+ serial: ,
+ clientId: ,
+ timestamp: ,
+ description: ,
+ metadata:
+ }
+}
+```
+
+
+An unsuccessful request returns an error. A 404 error is returned if a message with that serial does not exist.
+
+### Update a message
+
+#### PATCH main.realtime.ably.net/channels/\/messages/\
+
+Update an existing message on a channel, the message with the specified serial. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule.
+
+See [Updating and deleting messages](/docs/messages/updates-deletes#update) for more information about message updates and field semantics.
+
+The request body contains the message fields to update, along with optional operation metadata. Any fields not specified will be left as their original values.
+
+
+```json
+{
+ name: ,
+ data: ,
+ encoding: ,
+ extras: ,
+ operation: {
+ clientId: ,
+ description: ,
+ metadata:
+ }
+}
+```
+
+
+Example request:
+
+
+```shell
+curl -X PATCH https://main.realtime.ably.net/channels/rest-example/messages/01826232498871-001@abcdefghij:001 \
+ -u "{{API_KEY}}" \
+ -H "Content-Type: application/json" \
+ --data \
+ '{
+ "data": "updated message content",
+ "operation": {
+ "description": "Fixed typo"
+ }
+ }'
+```
+
+
+##### Options
+
+| Property | Value |
+|----------|-------|
+| Content-Type | `application/json`, `application/x-msgpack` or `application/x-www-form-urlencoded` |
+| Accept | `application/json` by default or `application/x-msgpack` |
+| Auth required | yes ([basic](basic-authentication) or [token](token-authentication) with `message-update-own` or `message-update-any` capability) |
+
+##### Capabilities
+
+- `message-update-own` := Can update your own messages (messages where the original publisher's `clientId` matches the updater's `clientId`, where both are [identified](/docs/auth/identified-clients))
+- `message-update-any` := Can update any message on the channel
+
+##### Returns
+
+A successful request returns the updated @Message@ object with the new version information.
+
+See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum.
+
+
+```json
+{
+ serial: ,
+ name: ,
+ data: ,
+ timestamp: ,
+ clientId: ,
+ action: ,
+ version: {
+ serial: ,
+ clientId: ,
+ timestamp: ,
+ description: ,
+ metadata:
+ }
+}
+```
+
+
+An unsuccessful request returns an error.
+
+### Delete a message
+
+#### POST main.realtime.ably.net/channels/\/messages/\/delete
+
+Delete a message on a channel, the message with the specified serial. This is a 'soft' delete that publishes a new version of the message with an action of `message.delete`. The full message history remains accessible through the [message versions](message-versions) endpoint. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule.
+
+See [Updating and deleting messages](/docs/messages/updates-deletes#update) for more information about message updates and field semantics.
+
+The request body contains the message fields to update, along with optional operation metadata. Any fields not specified will be left as their original values.
+
+
+```json
+{
+ name: ,
+ data: ,
+ encoding: ,
+ extras: ,
+ operation: {
+ clientId: ,
+ description: ,
+ metadata:
+ }
+}
+```
+
+
+Example request:
+
+
+```shell
+curl -X POST https://main.realtime.ably.net/channels/rest-example/messages/01826232498871-001@abcdefghij:001/delete \
+ -u "{{API_KEY}}" \
+ -H "Content-Type: application/json" \
+ --data \
+ '{
+ "operation": {
+ "description": "Content violation"
+ }
+ }'
+```
+
+
+##### Options
+
+| Property | Value |
+|----------|-------|
+| Content-Type | `application/json`, `application/x-msgpack` or `application/x-www-form-urlencoded` |
+| Accept | `application/json` by default, or `application/x-msgpack`, `text/html` |
+| Auth required | yes ([basic](basic-authentication) or [token](token-authentication) with `message-delete-own` or `message-delete-any` capability) |
+
+##### Capabilities
+
+| Capability | Description |
+|------------|-------------|
+| `message-delete-own` | Can delete your own messages (messages where the original publisher's `clientId` matches the deleter's `clientId`, where both are [identified](/docs/auth/identified-clients)) |
+| `message-delete-any` | Can delete any message on the channel |
+
+##### Returns
+
+A successful request returns the updated `Message` object with `action` set to `message.delete`.
+
+See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum.
+
+
+```shell
+{
+ serial: ,
+ name: ,
+ data: ,
+ timestamp: ,
+ clientId: ,
+ action: ,
+ version: {
+ serial: ,
+ clientId: ,
+ timestamp: ,
+ description: ,
+ metadata:
+ }
+}
+```
+
+
+An unsuccessful request returns an error.
+
+### Retrieve message version history
+
+#### GET main.realtime.ably.net/channels/\/messages/\/versions
+
+Retrieve all historical versions of a specific message, including the original and all subsequent updates or delete operations. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule.
+
+Example request:
+
+
+```shell
+ curl https://main.realtime.ably.net/channels/rest-example/messages/01826232498871-001@abcdefghij:001/versions \
+ -u "{{API_KEY}}"
+```
+
+
+##### Options
+
+| Property | Value |
+|----------|-------|
+| Content-Type | not applicable |
+| Accept | `application/json` by default, or `application/x-msgpack`, `text/html` |
+| Auth required | yes ([basic](basic-authentication) or [token](token-authentication) with `history` capability) |
+
+##### Returns
+
+A successful request returns a [paginated response](pagination) containing all versions of the message, ordered by version serial (oldest to newest).
+
+See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum.
+
+
+```json
+[{
+ serial: ,
+ name: ,
+ data: ,
+ timestamp: ,
+ clientId: ,
+ action: ,
+ version: {
+ serial: ,
+ clientId: ,
+ timestamp: ,
+ description: ,
+ metadata:
+ }
+}]
+```
+
+
+An unsuccessful request returns an error.
+
### Retrieve metadata for a channel
#### GET rest.ably.io/channels/\{channelId\}
diff --git a/src/pages/docs/api/rest-sdk/channels.mdx b/src/pages/docs/api/rest-sdk/channels.mdx
index 76b1d045e8..76235bf661 100644
--- a/src/pages/docs/api/rest-sdk/channels.mdx
+++ b/src/pages/docs/api/rest-sdk/channels.mdx
@@ -12,7 +12,7 @@ redirect_from:
The `Channels` object, accessed from the [rest library client constructor](/docs/api/rest-sdk#channels), is used to create and destroy `Channel` objects. It exposes the following public methods:
-### Channels Methods
+### Channels Methods
#### getGet
@@ -465,6 +465,85 @@ On failure to retrieve message history, the `error` contains an [`ErrorInfo`](#e
+
+
+#### getMessage
+
+`getMessage(serialOrMessage: string | Message): Promise`
+
+Retrieves the latest version of a specific message by its serial identifier. Requires the **history** [capability](/docs/auth/capabilities).
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| serialOrMessage | Either the serial identifier string of the message to retrieve, or a [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | `string` or [`Message`](/docs/api/realtime-sdk/messages) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object representing the latest version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### updateMessage
+
+`updateMessage(message: Message, operation?: MessageOperation, params?: Record): Promise`
+
+Publishes an update to an existing message with patch semantics. Non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field and the fields to update | [`Message`](/docs/api/realtime-sdk/messages) |
+| operation | An optional `MessageOperation` object containing metadata about the update operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object containing the updated message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### deleteMessage
+
+`deleteMessage(message: Message, operation?: MessageOperation, params?: Record): Promise`
+
+Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses patch semantics: non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | [`Message`](/docs/api/realtime-sdk/messages) |
+| operation | An optional `MessageOperation` object containing metadata about the delete operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`Message`](/docs/api/realtime-sdk/messages) object containing the deleted message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+#### getMessageVersions
+
+`getMessageVersions(serialOrMessage: string | Message, params?: Record): Promise>`
+
+Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. Requires the **history** [capability](/docs/auth/capabilities).
+
+See [updating and deleting messages](/docs/messages/updates-deletes) for more information.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| serialOrMessage | Either the serial identifier string of the message whose versions are to be retrieved, or a [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | `string` or [`Message`](/docs/api/realtime-sdk/messages) |
+| params | Optional parameters sent as part of the query string | `Record` (optional) |
+
+##### Returns
+
+Returns a promise which, upon success, will be fulfilled with a [`PaginatedResult`](#paginated-result) object containing an array of [`Message`](/docs/api/realtime-sdk/messages) objects representing all versions of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error.
+
+
+
## Related types
### MessageARTMessageAbly::Models::MessageAbly\Models\Messageio.ably.lib.types.MessageIO.Ably.Message
@@ -507,20 +586,24 @@ Timestamp when the message was first received by the Ably, as
+
-#### action
+### action
-The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
+The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
-#### serial
+### serial
-A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in [channel rules](/docs/channels#rules) . _Type: `String`_
+A server-assigned identifier that will be the same in all future updates of this message. It can be used to add [annotations](/docs/messages/annotations) to a message or to [update or delete](/docs/messages/updates-deletes) it. Serial will only be set if you enable annotations, updates, and deletes in [channel rules](/docs/channels#rules). _Type: `String`_
-#### annotations
+### annotations
An object containing information about annotations that have been made to the object. _Type: [`MessageAnnotations`](/docs/api/realtime-sdk/types#message-annotations)_
+### version
+
+An object containing version metadata for messages that have been updated or deleted. See [updating and deleting messages](/docs/messages/updates-deletes) for more information. _Type: [`MessageVersion`](#message-version)_
+
### Message constructors
diff --git a/src/pages/docs/api/rest-sdk/messages.mdx b/src/pages/docs/api/rest-sdk/messages.mdx
index b774a90ad2..3312386224 100644
--- a/src/pages/docs/api/rest-sdk/messages.mdx
+++ b/src/pages/docs/api/rest-sdk/messages.mdx
@@ -48,15 +48,15 @@ This will typically be empty as all messages received from Ably are automaticall
-### action
+#### action
The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. _Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_
-### serial
+#### serial
-A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in [channel rules](/docs/channels#rules). _Type: `String`_
+A server-assigned identifier that will be the same in all future updates of this message. It can be used to add annotations to a message. Serial will only be set if you enable annotations in [channel rules](/docs/channels#rules) . _Type: `String`_
-### annotations
+#### annotations
An object containing information about annotations that have been made to the object. _Type: [`MessageAnnotations`](/docs/api/realtime-sdk/types#message-annotations)_
@@ -97,3 +97,15 @@ A static factory method to create an array of [`Messages`](/docs/api/realtime-sd
#### Returns
An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects
+
+## MessageVersion
+
+#### Properties
+
+| Property | Description | Type |
+|----------|-------------|------|
+| serial | An Ably-generated ID that uniquely identifies this version of the message. Can be compared lexicographically to determine version ordering. For an original message with an action of `message.create`, this will be equal to the top-level `serial`. | `String` |
+| timestamp | The time this version was created (when the update or delete operation was performed). For an original message, this will be equal to the top-level `timestamp`. | `Integer``Long Integer``DateTimeOffset``Time``NSDate` |
+| clientId | The client identifier of the user who performed the update or delete operation. Only present for `message.update` and `message.delete` actions. | `String` (optional) |
+| description | Optional description provided when the update or delete was performed. Only present for `message.update` and `message.delete` actions. | `String` (optional) |
+| metadata | Optional metadata provided when the update or delete was performed. Only present for `message.update` and `message.delete` actions. | `Object` (optional) |
diff --git a/src/pages/docs/auth/capabilities.mdx b/src/pages/docs/auth/capabilities.mdx
index 1551dac175..5611b30067 100644
--- a/src/pages/docs/auth/capabilities.mdx
+++ b/src/pages/docs/auth/capabilities.mdx
@@ -42,6 +42,10 @@ The following capability operations are available for API keys and issued tokens
| **object-publish** | Can update objects on a channel |
| **annotation-subscribe** | Can subscribe to individual annotations on a channel |
| **annotation-publish** | Can publish annotations to messages on a channel |
+| **message-update-own** | Can [update messages](/docs/messages/updates-deletes) where the original publisher's `clientId` matches the updater's `clientId` |
+| **message-update-any** | Can [update any message](/docs/messages/updates-deletes) on the channel |
+| **message-delete-own** | Can [delete messages](/docs/messages/updates-deletes) where the original publisher's `clientId` matches the deleter's `clientId` |
+| **message-delete-any** | Can [delete any message](/docs/messages/updates-deletes) on the channel |
| **history** | Can retrieve message and presence state history on channels |
| **stats** | Can retrieve current and historical usage statistics for an app |
| **push-subscribe** | Can subscribe devices for push notifications |
diff --git a/src/pages/docs/messages/index.mdx b/src/pages/docs/messages/index.mdx
index 5c41d59998..f858d2675a 100644
--- a/src/pages/docs/messages/index.mdx
+++ b/src/pages/docs/messages/index.mdx
@@ -34,8 +34,9 @@ The following are the properties of a message:
| **extras** | A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include those related to [Push Notifications](/docs/push), [deltas](/docs/channels/options/deltas) and headers |
| **encoding** | This is typically empty, as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute contains the remaining transformations not applied to the data payload |
| **action** | An [enum](/docs/api/realtime-sdk/types#message-action) telling you whether this is a normal ('create') message, an update to a previous message, an annotation summary, etc. |
-| **serial** | The message's serial (a server-assigned identifier that will be the same in all future updates of this message, and can be used to add [annotations](/docs/messages/annotations)). Right now this will only be set if you enable annotations in [channel rules](/docs/channels#rules) |
+| **serial** | The message's serial (a server-assigned identifier that will be the same in all future updates of this message, and can be used to add [annotations](/docs/messages/annotations) or to [update or delete](/docs/messages/updates-deletes) the message). This will only be set if you enable annotations, updates, and deletes in [channel rules](/docs/channels#rules) |
| **annotations** | An object containing a summary of any [annotations](/docs/messages/annotations) that have been made to the message |
+| **version** | An object containing [version metadata](/docs/messages/updates-deletes#version-structure) for messages that have been updated or deleted |
## Message conflation
diff --git a/src/pages/docs/messages/updates-deletes.mdx b/src/pages/docs/messages/updates-deletes.mdx
new file mode 100644
index 0000000000..911d6aab48
--- /dev/null
+++ b/src/pages/docs/messages/updates-deletes.mdx
@@ -0,0 +1,344 @@
+---
+title: Updating and deleting messages
+meta_description: "Update and delete messages published to a channel, and retrieve message version history."
+---
+
+
+
+You can update and delete messages that have been published to a channel, for use cases such as:
+
+* **Message editing** - allow users to edit their messages in chat-like applications
+* **Content moderation** - remove or edit inappropriate content after publication
+* **Gradual message building** - a message can be published while still unfinished, and then repeatedly edited with more complete information, so that someone querying history once the message is complete will only see the final version
+
+Updating or deleting a message does not modify any messages that have been received by subscribing clients in-place: a given Message object is immutable. Rather, it publishes a new message to the channel, with an action of `message.update` or `message.delete`, with the same `serial` as the original message, that subscribing clients can see and act on.
+
+It also replaces the original message in message history, so history queries will see the latest version of the message (but in the place in history of the original).
+
+You can specify metadata (such as the reason for the update and which client is doing the update), which is published along with it.
+
+You can access the full version history of any given message.
+
+## Enable message updates and deletes
+
+Message updates and deletes can be enabled for a channel or channel namespace with the *Message annotations, updates, and deletes* channel rule.
+
+
+
+1. Go to the **Settings** tab of an app in your dashboard.
+2. Under [channel rules](/docs/channels#rules), click **Add new rule**.
+3. Enter the channel name or channel namespace on which to enable message updates and deletes.
+4. Check **Message annotations, updates, and deletes** to enable the feature.
+5. Click **Create channel rule** to save.
+
+## Update a message
+
+To update an existing message, use the `updateMessage()` method on a REST or realtime channel. The published update will have an `action` of `message.update`.
+
+The message is identified by its `serial`, which is populated by Ably. So to publish an update to a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its `serial` to that of the original message, as appropriate in your usecase.
+
+
+```javascript
+const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
+// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
+const channel = realtime.channels.get('updates:example');
+
+// First subscribe to messages
+await channel.subscribe((msg) => {
+ if (msg.data === 'original-data') {
+ // Publish an update
+
+ // First way: mutate the received Message
+ msg.data = 'new-data-1';
+ await channel.updateMessage(msg, { description: 'reason for first update' });
+
+ // Second way: publish a new Message using the serial
+ const msg2 = {
+ name: 'message-name',
+ serial: msg.serial,
+ };
+ await channel.updateMessage(msg2, { description: 'reason for second update' });
+ }
+});
+
+// Publish the original message
+await channel.publish({
+ name: 'message-name',
+ data: 'original-data',
+});
+```
+
+```nodejs
+const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
+// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
+const channel = realtime.channels.get('updates:example');
+
+// First subscribe to messages
+await channel.subscribe((msg) => {
+ if (msg.data === 'original-data') {
+ // Publish an update
+ // First way: mutate the received Message
+ msg.data = 'new-data-1';
+ await channel.updateMessage(msg, { description: 'reason for first update' });
+
+ // Second way: publish a new Message using the serial
+ const msg2 = {
+ name: 'message-name',
+ serial: msg.serial,
+ };
+ await channel.updateMessage(msg2, { description: 'reason for second update' });
+ }
+});
+
+// Publish the original message
+await channel.publish({
+ name: 'message-name',
+ data: 'original-data',
+});
+```
+
+
+#### Patch semantics
+
+When updating a message, the fields you specify in the update will replace the corresponding fields in the existing message, and other fields will remain as they were. For example, if a message has `{ name: "greeting", data: "hello" }`, and you update it with `{ data: "hi" }`, the result will be `{ name: "greeting", data: "hi" }`.
+
+The fields that can be updated are:
+- `data`
+- `name`
+- `extras`
+
+#### Capabilities
+
+To update messages, clients need one of the following [capabilities](/docs/auth/capabilities):
+
+| Capability | Description |
+| ---------- | ----------- |
+| **message-update-own** | Can update your own messages (more precisely, messages where the original publisher's `clientId` matches the updater's `clientId`, where both are [identified](/docs/auth/identified-clients)). |
+| **message-update-any** | Can update any message on the channel. |
+
+#### Operation metadata
+
+When updating a message, you can optionally provide metadata about the update operation:
+
+| Property | Description | Type |
+| -------- | ----------- | ---- |
+| clientId | The client identifier of the user performing the update (automatically populated if the delete is done by an identified client). | String |
+| description | A description of why the update was made. | String |
+| metadata | Additional metadata about the update operation. | Object |
+
+This metadata will end up in the message's `version` property. See [Message version structure](/docs/messages/updates-deletes#version-structure) for what this looks like.
+
+## Delete a message
+
+To delete a message, use the `deleteMessage()` method on a REST or realtime channel. This is very much a 'soft' delete: it's just an update, but with an `action` of `message.delete` instead of `message.update`. It's up to your application to interpret it.
+
+The latest version of each message will be accessible from history, including if that latest version happens to be a delete.
+
+As with updating, the message is identified by its `serial`, which is populated by Ably. So to delete a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its `serial` to that of the original message, as appropriate in your usecase.
+
+Deleting a message marks it as deleted without removing it from the server. The full message history remains accessible through the [message versions](#versions) API.
+
+
+```javascript
+const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
+// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
+const channel = realtime.channels.get('updates:example');
+
+// First subscribe to messages
+await channel.subscribe((msg) => {
+ if (msg.data === 'original-data') {
+ // Publish a delete
+ // First way: just use the received Message
+ await channel.deleteMessage(msg, { description: 'reason for first delete' });
+
+ // Second way: publish a new Message using the serial
+ const msg2 = { serial: msg.serial };
+ await channel.deleteMessage(msg2, { description: 'reason for second delete' });
+ }
+});
+
+// Publish the original message
+await channel.publish({
+ name: 'message-name',
+ data: 'original-data',
+});
+```
+
+```nodejs
+const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
+// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
+const channel = realtime.channels.get('updates:example');
+
+// First subscribe to messages
+await channel.subscribe((msg) => {
+ if (msg.data === 'original-data') {
+ // Publish a delete
+ // First way: just use the received Message
+ await channel.deleteMessage(msg, { description: 'reason for first delete' });
+
+ // Second way: publish a new Message using the serial
+ const msg2 = { serial: msg.serial };
+ await channel.deleteMessage(msg2, { description: 'reason for second delete' });
+ }
+});
+
+// Publish the original message
+await channel.publish({
+ name: 'message-name',
+ data: 'original-data',
+});
+```
+
+
+#### Patch semantics
+
+Deleting has the same semantics as updating, so only fields you specify in the update will replace the corresponding fields in the existing message.
+
+That means that if you e.g. want the deleted message to have empty `data` (to prevent users looking at raw history results from the API from seeing what the data used to be), you must explicitly set to e.g. an empty object when publishing the delete. (And even then, all previous versions are accessible through the version history API).
+
+As with update, the fields that can be updated are `data`, `name`, and `extras`.
+
+#### Capabilities
+
+To delete messages, clients need one of the following [capabilities](/docs/auth/capabilities):
+
+| Capability | Description |
+| ---------- | ----------- |
+| **message-delete-own** | Can delete your own messages (more precisely, messages where the original publisher's `clientId` matches the deleter's `clientId`, where both are [identified](/docs/auth/identified-clients)). |
+| **message-delete-any** | Can delete any message on the channel. |
+
+#### Operation metadata
+
+When deleting a message, you can optionally provide metadata:
+
+| Property | Description | Type |
+| -------- | ----------- | ---- |
+| clientId | The client identifier of the user performing the delete (automatically populated if the delete is done by an identified client). | String |
+| description | A description of why the delete was made. | String |
+| metadata | Additional metadata about the delete operation. | Object |
+
+This metadata will end up in the message's `version` property. See [Message version structure](/docs/messages/updates-deletes#version-structure) for what this looks like.
+
+## Get the latest version of a message
+
+To retrieve the most recent version of a specific message, use the `getMessage()` method on a REST channel. You can pass either the message's serial identifier as a string, or a message object with a `serial` property.
+
+This operation requires the history [capability](/docs/auth/capabilities).
+
+
+```javascript
+const rest = new Ably.Rest({ key: '{{API_KEY}}' });
+const channel = rest.channels.get('updates:example');
+
+// could also use msg.serial, useful if you want to retrieve a
+// message for a serial you have stored or passed around
+const message = await channel.getMessage(msg);
+```
+
+```nodejs
+const rest = new Ably.Rest({ key: '{{API_KEY}}' });
+const channel = rest.channels.get('updates:example');
+
+// could also use msg.serial, useful if you want to retrieve a
+// message for a serial you have stored or passed around
+const message = await channel.getMessage(msg);
+```
+
+
+## Get message versions
+
+To retrieve all historical versions of a message, use the `getMessageVersions()` method. This returns a paginated result containing all versions of the message, including the original and all subsequent updates or delete operations, ordered by version.
+
+This operation requires the history [capability](/docs/auth/capabilities).
+
+
+```javascript
+const rest = new Ably.Rest({ key: '{{API_KEY}}' });
+const channel = rest.channels.get('updates:example');
+
+const page = await channel.getMessageVersions(msg);
+console.log(`Found ${page.items.length} versions`);
+```
+
+```nodejs
+const rest = new Ably.Rest({ key: '{{API_KEY}}' });
+const channel = rest.channels.get('updates:example');
+
+const page = await channel.getMessageVersions(msg);
+console.log(`Found ${page.items.length} versions`);
+```
+
+
+## Message version structure
+
+A published update or delete contains version metadata in the `version` property. The following shows the structure of a message after it has been updated:
+
+
+```json
+{
+ // The top-level serial is a permanent identifier of the message, and remains
+ // the same for all updates and deletes of that message
+ "serial": "01826232498871-001@abcdefghij:001",
+
+ // The clientId of the user who published the original message
+ "clientId": "user123",
+
+ // The timestamp of that original publish
+ "timestamp": 1718195879988,
+
+ // Main payload fields
+ "name": "greeting",
+ "data": "hello world (edited)",
+
+ // The action tells you if it's an original ("message.create"), update,
+ // delete, or annotation summary
+ "action": "message.update",
+
+ "version": {
+ // The serial of the current version. For an original (with an action of
+ // "message.create"), this will be equal to the top-level serial. You can
+ // use this to compare different versions to see which one is more recent
+ "serial": "01826232512345-002@abcdefghij:002",
+
+ // The clientId of the user who made the update
+ "clientId": "user123",
+
+ // The timestamp of this latest version
+ "timestamp": 1718195912345,
+
+ // Update metadata, supplied by the user who published the update
+ "description": "Fixed typo",
+ "metadata": {
+ "reason": "correction"
+ }
+ }
+}
+```
+
+
+#### Message actions
+
+The `action` property on a message indicates the type of operation:
+
+| Action | Description |
+| ------ | ----------- |
+| message.create | The original message |
+| message.update | An update to an existing message |
+| message.delete | A deletion of a message |
+| meta | A message originating from ably rather than being published by a user, such as [inband occupancy events](/docs/channels/options#occupancy) |
+| message.summary | TM5 | A message containing the [latest rolled-up summary of annotations](/docs/messages/annotations#annotation-summaries) |
+
+
+## Version ordering
+
+Both the message `serial` and `version.serial` are lexicographically sortable strings, providing a deterministic ordering of messages and their versions.
+
+To determine which version of a message is newer, compare the `version.serial` values.
+
+In the case of updates or deletes made by different users at similar times, both will be published on the channel, but the one that is assigned the lexicographically-highest `version.serial` will 'win', in the sense that retrieving channel history will eventually always return that version of the message.