From 4ac01f5b08d879d96004928453eef0ab23e6d7c0 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 13:01:33 +0200
Subject: [PATCH 01/14] Can I delete messages which have been persisted through
the history API?
---
src/pages/docs/storage-history/storage.mdx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/pages/docs/storage-history/storage.mdx b/src/pages/docs/storage-history/storage.mdx
index 049f754672..5eaf4a970d 100644
--- a/src/pages/docs/storage-history/storage.mdx
+++ b/src/pages/docs/storage-history/storage.mdx
@@ -27,6 +27,10 @@ The time that messages will be stored for depends on your account package:
There is a cost associated with storing messages for longer than the minimum time period. [Contact us](https://ably.com/support) to discuss enabling long term storage.
+### Message deletion
+
+Ably does not currently provide an API to delete persisted messages from the history. Once messages are stored with persisted history enabled, they will remain for the entire configured storage period. If you need to delete specific messages from history, please [contact us](https://ably.com/support) to discuss your requirements.
+
Messages can be retrieved using the [history](/docs/storage-history/history) feature. This is illustrated in the following diagram:

From cf80031f7134687e906ec53eb6d222d87ed424f0 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 13:13:26 +0200
Subject: [PATCH 02/14] Is it possible to check if a specific message has been
delivered to a device?
---
src/pages/docs/messages/index.mdx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/pages/docs/messages/index.mdx b/src/pages/docs/messages/index.mdx
index e30609c3ce..511c4f9306 100644
--- a/src/pages/docs/messages/index.mdx
+++ b/src/pages/docs/messages/index.mdx
@@ -34,6 +34,12 @@ 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 |
+## Message delivery tracking
+
+You can ensure a message was successfully published by checking the [history](/docs/storage-history/history) of the channel for your message. However, it is only possible to check if a device has received a message from the device itself.
+
+Ably does not store per-message delivery logs, nor logs of who is subscribed to a channel at any point in time. This means it is not possible to check which users have received messages retroactively.
+
## Message conflation
Use message conflation to ensure that clients only ever receive the most up-to-date message, by removing redundant and outdated messages. Message conflation will aggregate published messages for a set period of time and evaluate all messages against a [conflation key](#routing). All but the latest message for each conflation key value will be discarded, and the resulting message, or messages, will be delivered to subscribers as a single batch once the period of time elapses.
From 48500cda20e272477bc51e2cdf5a2c367c74a1eb Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 13:18:10 +0200
Subject: [PATCH 03/14] Reliable message ordering for connected clients
---
.../architecture/message-ordering.mdx | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/pages/docs/platform/architecture/message-ordering.mdx b/src/pages/docs/platform/architecture/message-ordering.mdx
index 3cdd3bca52..458799b0a5 100644
--- a/src/pages/docs/platform/architecture/message-ordering.mdx
+++ b/src/pages/docs/platform/architecture/message-ordering.mdx
@@ -58,3 +58,26 @@ When building globally distributed applications, developers should understand th
The dual ordering system allows applications to choose the appropriate consistency model for their needs. For highly interactive applications where responsiveness is critical, realtime order minimizes latency. For applications that require a consistent historical record, the canonical global order provides a stable view that all components can agree on.
To support both ordering systems efficiently, messages are stored with metadata that tracks their position in multiple sequences. This allows the platform to rapidly retrieve messages in either realtime or canonical global order as needed for different API operations or connection recovery scenarios.
+
+## Message ordering guarantees
+
+Ably ensures that messages are delivered to persistently connected subscribers on a channel in the order they were published when using the Realtime libraries. Each message sent on a realtime connection has a unique incrementing serial number, enabling this ordering guarantee.
+
+For example, if you publish messages using a Realtime client library, subscribers on that channel anywhere in the world will receive those messages in the same order they were originally published.
+
+### REST publishing considerations
+
+When publishing using REST client libraries, Ably still guarantees that the order messages are received will be honoured for all subscribers. However, if you are sending messages at a high rate with separate REST requests, it is possible that a later HTTP request may reach Ably before a previous request due to variable network factors outside of Ably's control.
+
+If ordering is important to you when using REST, consider these approaches:
+- Rate limit your HTTP requests
+- [Batch messages](/docs/messages/batch) together in a single request
+- Use Realtime client libraries to publish messages instead
+
+### Connection recovery edge cases
+
+Ably supports connection state recovery, so even if a connection is lost and re-established, messages replayed when reconnected will be in sequential order.
+
+However, there is a rare situation where ordering may not be maintained: if a client is disconnected and the server maintaining that client's connection state is terminated or recycled during connection state recovery, messages may potentially be replayed out of order. This behaviour is by design to ensure that the backlog of messages held in the connection state do not prevent the new server from sending realtime messages to clients.
+
+If message ordering is required in all cases, consider catching the connection disconnected event and either re-establishing a new connection or manually re-ordering incoming messages following disconnection.
From ee1d62856e2a81a427758c9e321a83e5ef507291 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 14:10:28 +0200
Subject: [PATCH 04/14] Client-specified message ID restrictions for multiple
messages published atomically
---
src/pages/docs/pub-sub/advanced.mdx | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/pages/docs/pub-sub/advanced.mdx b/src/pages/docs/pub-sub/advanced.mdx
index 46f2c736be..168c19edaa 100644
--- a/src/pages/docs/pub-sub/advanced.mdx
+++ b/src/pages/docs/pub-sub/advanced.mdx
@@ -954,7 +954,32 @@ In some cases you may wish to set the unique message ID yourself to achieve idem
* To ensure idempotency when a publisher instance might be restarted, and continuous activity cannot be guaranteed.
* To integrate with an upstream system that uses message IDs, to ensure idempotency across an entire message processing pipeline.
-If setting your own message IDs be aware of the [restrictions](https://faqs.ably.com/client-specified-message-id-restrictions-for-multiple-messages-published-atomically) on its format when publishing messages atomically.
+If setting your own message IDs be aware of the restrictions on its format when publishing messages atomically.
+
+#### Client-specified message ID restrictions for multiple messages published atomically
+
+When publishing multiple messages in a single publish request (via REST or realtime), those messages are published atomically by Ably. They are bundled together, and will either all succeed or all fail.
+
+##### Idempotency with atomic publishes
+
+From the point of view of idempotency, there is only a single idempotency 'key' for the entire array of messages. Ably prevents you from including a different, unrelated `id` in each message to avoid the mistaken impression that the messages will be individually idempotent.
+
+For example, if Ably permitted `publish([{ id: 'foo', data: 'first' },{ id: 'bar', data: 'second' }])`, it might seem like you could later call `publish([{ id: 'bar', data: 'second' }])` and the second publish would be ineffective due to idempotency; but that is not the case.
+
+##### ID format requirements
+
+For messages published atomically, all messages must have an `id` derived from a single base ID. Each message must contain an `id` of the form ` :` where `idx` is a zero-based index into the array of messages.
+
+For example, to publish 3 messages with a base ID of `foo`, the messages must have IDs `foo:0`, `foo:1`, `foo:2`. This emphasizes that the messages share a single 'idempotency key' (which will be `foo`).
+
+##### Publishing messages with separate idempotency
+
+If you want messages to be separately and individually idempotent, you should publish them non-atomically:
+
+* Issue multiple `publish()` calls serially instead of a single `publish()` call with an array of messages.
+
+* If publishing over REST and you want all publishes to happen with a single HTTP request, use the batch publishing API. Each message is contained in its own `batchspec` object. All messages in a single `batchspec` are published atomically (for each channel in that `batchspec`), so by specifying multiple `batchspecs`, you can publish messages non-atomically, allowing you to specify a different idempotency ID for each message.
+
Ably can only detect duplicate messages within a 2-minute window after the original message, with the same ID, is published. If a message with the same ID is published after this 2-minute window, it will be treated as a new message.
From d1d86fcd2d963f310246fa0e733d6b441b9f0d96 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 14:53:31 +0200
Subject: [PATCH 05/14] Can I see messages sent by Channel or ClientID?
---
src/pages/docs/platform/account/app/console.mdx | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/pages/docs/platform/account/app/console.mdx b/src/pages/docs/platform/account/app/console.mdx
index ae9462472f..69a6ab7876 100644
--- a/src/pages/docs/platform/account/app/console.mdx
+++ b/src/pages/docs/platform/account/app/console.mdx
@@ -24,6 +24,18 @@ The following explains the realtime monitoring tools in the application-wide eve
| **Average application-wide events per second (p/s)** | This metric shows the average number of events occurring per second across your application. For example, if the current rate is 0, no active events are being processed. |
| **Event log table** | The event log table displays a record of events related to the current client's connection status. This table can be used to debug potential issues in your application. |
+## Message auditing and logging
+
+The dev console displays messages in realtime for debugging and testing purposes, but does not provide persistent message auditing or logging capabilities. Ably does not currently offer native functionality to view historical messages filtered by specific channels or client IDs for auditing purposes.
+
+If you need to audit or log messages by channel or client ID, you must implement this functionality on your application side. Consider using:
+
+- [Webhooks](/docs/platform/integrations/webhooks) to send message events to your logging system,
+- [Message queues](/docs/platform/integrations/queues) to process and store message data,
+- Client-side logging in your application code,
+
+If you're interested in native message auditing features, please [contact support](mailto:support@ably.com) to discuss your requirements.
+
## Channels
The following is a step-by-step instructions for connecting to a channel, publishing messages.
From 009d1fb689fc1540ada2a49e7090afe5e2ea8d5e Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 19:10:46 +0200
Subject: [PATCH 06/14] How can I implement presence subscriptions efficiently,
in terms of limits?
---
src/pages/docs/presence-occupancy/presence.mdx | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index ec6ac68450..0e76e66536 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -623,7 +623,7 @@ Using server-side batching for presence events can reduce message costs when the
Messages are streamed to clients as soon as they [attach](/docs/channels/states#attach) to a channel, as long as they have the [subscribe capability](/docs/auth/capabilities).
-Efficient use of capabilities can prevent clients from receiving presence events while still allowing them to enter the presence set. This is a common scenario where only a server is required to monitor the presence set. This saves clients from subscribing to a potentially high volume of unnecessary messages.
+Efficient use of capabilities can prevent clients from receiving presence events while still allowing them to enter the presence set. This is a common scenario when you want lots of people to be present without necessarily listening for presence change events, such as when you just want to know how many people are present on a channel at any point in time. This approach saves clients from subscribing to a potentially high volume of unnecessary messages.
One example of achieving this would be to use one channel for generic communications and another for the presence set. The following capabilities demonstrate this for clients and for servers:
@@ -649,6 +649,17 @@ For servers:
```
+Another approach is to use differential token capabilities on a single channel. The following example shows capabilities where clients can enter presence and publish, but only have subscribe access to regular messages:
+
+
+```json
+{
+ "presenceChannel": ["publish", "presence"],
+ "chat": ["presence", "history", "subscribe"]
+}
+```
+
+
Alternatively, [channel mode flags](/docs/channels/options#modes) can be used to enable clients to be present on a channel without subscribing to presence events. This also enables clients to still subscribe to regular messages on the channel.
## Retrieve presence members
From fe09bff90b0d2a8879405e625e19e04cf46f54fb Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 19:15:43 +0200
Subject: [PATCH 07/14] Can I use webhooks or other rules to maintain an
external copy of the presence set of a channel or channels?
---
.../docs/presence-occupancy/presence.mdx | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index 0e76e66536..a9bd65ba16 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -662,6 +662,35 @@ Another approach is to use differential token capabilities on a single channel.
Alternatively, [channel mode flags](/docs/channels/options#modes) can be used to enable clients to be present on a channel without subscribing to presence events. This also enables clients to still subscribe to regular messages on the channel.
+## External presence set maintenance
+
+While it's technically possible to use [webhooks](/docs/platform/integrations/webhooks) or other integration rules to maintain an external copy of a channel's presence set, this approach is generally not recommended for several important reasons:
+
+### Implementation complexity
+
+Combining presence events into a presence set is not trivial to implement. Presence events are fundamentally a stream of delta updates to a CRDT (Conflict-free Replicated Data Type). These updates must be merged into your local copy following a detailed protocol designed to ensure:
+
+* Events can be applied in any order (commutative).
+* Synthetic leave events are handled correctly.
+* Members are properly identified by both `clientId` and `connectionId`.
+* State conflicts are resolved consistently.
+
+Ably's realtime client libraries handle this complex logic automatically, but external implementations must replicate this behavior correctly.
+
+### No synchronization mechanism
+
+External integration rules receive only a stream of updates with no way to retrieve the complete current state. If your server goes down or fails to process messages, you become permanently out of sync with no built-in way to recover. Key limitations include:
+
+* Ably retries failed webhooks within limits, but sufficiently old messages are eventually discarded.
+* There are caps on messages per webhook post and total messages per second.
+* Missing events cannot be retrieved after the fact.
+
+### If you must use external maintenance
+
+If you decide to proceed with webhook-based presence maintenance despite these limitations, you can use [`PresenceMessage.fromEncodedArray()`](/docs/api/realtime-sdk/presence#presence-from-encoded-array) to decode presence message arrays and translate numerical actions into readable strings. This method also handles data decoding and decryption if you're using [encryption](/docs/channels/options/encryption).
+
+For applications requiring an always-up-to-date presence set, we strongly recommend using Ably's realtime client libraries and attaching to the channel directly.
+
## Retrieve presence members
The membership of the presence set can be retrieved by calling the [`get()`](/docs/api/realtime-sdk/presence#get) method on the `Presence` object of a channel. This returns an array of all members currently present on the channel and is available using the REST and realtime interfaces of an Ably SDK.
From 537780c57f0dfcb52276ac50613e343e4626bfdf Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 19:19:53 +0200
Subject: [PATCH 08/14] Why don't presence members leave as soon as I close a
tab?
---
.../docs/presence-occupancy/presence.mdx | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index a9bd65ba16..e22001526d 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -1263,6 +1263,18 @@ The Ably SDK will attempt to [reconnect](/docs/connect/states) after a disconnec
Note that the 15 second delay from being removed from the presence set is only for abrupt or unplanned disconnects. If a client calls [`leave()`](/docs/api/realtime-sdk/presence#leave) or [`close()`](/docs/api/realtime-sdk/connection#close) they immediately send a `leave` event.
+<<<<<<< HEAD
+### Browser tab close behavior
+
+When using the JavaScript client library (ably-js), you may notice that closing a browser tab results in a 15-second delay before presence members leave the presence set. By default, the client library closes the connection when the page is closed or refreshed, which should cause presence members to leave immediately. However, this may not occur in certain scenarios:
+
+* If you have explicitly set the `closeOnUnload` client option to `false` (for example, when using connection state recovery), the connection won't close immediately on page unload. This option defaults to `true` in current versions.
+* When using the `recover` client option and closing a tab (rather than refreshing), presence members remain for 15 seconds. The recover option stores the recovery key in the browser's localStorage to resume connections after page refresh, but cannot distinguish between tab close and refresh events.
+* Some browsers don't allow sufficient time for the `beforeunload` handler to notify the server when tabs are closed or refreshed. This affects some versions of Safari, Internet Explorer, and other browsers.
+* When Chrome's Memory Saver feature discards tabs, it doesn't fire a `beforeunload` event, resulting in the 15-second delay before presence members are removed.
+
+=======
+>>>>>>> 6b5b1525c (fixup! Why don't presence members leave as soon as I close a tab?)
The time taken before a `leave` event is sent in the case of an abrupt disconnect can be reduced to a minimum of 1 second by setting a value for `remainPresentFor`, in milliseconds. This property is set within the `transportParams` property of the [`clientOptions`](/docs/api/realtime-sdk#client-options) object.
It is important to note that it can initially take up to 30 seconds to identify that a client has been abruptly disconnected. Shorten the amount of time taken to identify abrupt disconnects using the [`heartbeatInterval`](/docs/connect#heartbeats) property if your app needs to quickly identify presence set members being abruptly disconnected.
@@ -1294,3 +1306,12 @@ options.transportParams = new Param[] { new Param("remainPresentFor", "1000") };
AblyRealtime ablyRealtime = new AblyRealtime(options);
```
+
+### Browser tab close behavior
+
+You may notice that closing a browser tab results in a 15-second delay before presence members leave the presence set. By default, the client library closes the connection when the page is closed or refreshed, which should cause presence members to leave immediately. However, this may not occur in certain scenarios:
+
+* If you have explicitly set the `closeOnUnload` client option to `false` (for example, when using connection state recovery), the connection won't close immediately on page unload. This option defaults to `true` in current versions.
+* When using the `recover` client option and closing a tab (rather than refreshing), presence members remain for 15 seconds. The recover option stores the recovery key in the browser's localStorage to resume connections after page refresh, but cannot distinguish between tab close and refresh events.
+* Some browsers don't allow sufficient time for the `beforeunload` handler to notify the server when tabs are closed or refreshed. This affects some versions of Safari, Internet Explorer, and other browsers.
+* When Chrome's Memory Saver feature discards tabs, it doesn't fire a `beforeunload` event, resulting in the 15-second delay before presence members are removed.
From e48509367430177709b98f164c5817a1cd7a8896 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 09:52:51 +0200
Subject: [PATCH 09/14] I don't understand why I see so many presence messages
in my stats. What happens if I exceed the number of members present on a
channel?
---
src/pages/docs/presence-occupancy/presence.mdx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index e22001526d..c325ca8e37 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -596,6 +596,8 @@ await channel.presence.enter();
The number of clients that can be simultaneously present on a channel is [limited](/docs/platform/pricing/limits#channel). This ensures the rate of presence messages remains supportable, as it is common for all members on a channel to change state at a similar time.
+Understanding presence message scaling is important because each presence update (enter, update, leave) is counted as a message for billing purposes, and presence follows an n-squared pattern when you have both subscribers and members present on a channel. This can lead to a surprisingly large number of messages being generated in a short time.
+
As an example, consider 200 clients subscribed to presence events on a channel and all of them join and leave the presence set within a few minutes. This would result in the following messages:
* 200 presence messages published for the enter event.
@@ -603,7 +605,7 @@ As an example, consider 200 clients subscribed to presence events on a channel a
* 200 presence messages published for the leave event.
* 200 × 200 (40,000) presence messages subscribed to for the leave events.
-This highlights the potential for 80,400 messages to be sent in a very short space of time on a single channel.
+This highlights the potential for 80,400 messages to be sent in a very short space of time on a single channel, which could result in rate limit issues or unexpected message costs.
If your application needs to have all clients subscribed to presence messages then [enabling server-side batching](#server-side-batch) can reduce the number of events received by clients. If your application doesn't need to have all clients subscribed then you can set some of them to [be present without subscribing](#present-no-subscribe).
From 6abe73e74676672919a6d5b2357cf9f9769a4ffb Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:06:12 +0200
Subject: [PATCH 10/14] What does it mean if I see 'Channel Region Inactive' in
my logs on the Dev Console?
---
src/pages/docs/metadata-stats/metadata/subscribe.mdx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/pages/docs/metadata-stats/metadata/subscribe.mdx b/src/pages/docs/metadata-stats/metadata/subscribe.mdx
index 3ee6efbe34..38c93c895c 100644
--- a/src/pages/docs/metadata-stats/metadata/subscribe.mdx
+++ b/src/pages/docs/metadata-stats/metadata/subscribe.mdx
@@ -56,6 +56,12 @@ The following events are published to `[meta]channel.lifecycle`:
The `data` property of all events is a [`ChannelDetails`](/docs/api/realtime-sdk/channel-metadata#channel-details) object. The [`ChannelDetails.ChannelStatus`](/docs/api/realtime-sdk/channel-metadata#channel-status) which includes [occupancy](/docs/presence-occupancy/occupancy) details varies depending on the event. If the event is specific to a region, such as `channel.region.active` then the occupancy metrics will only be for that region. For other events such as `channel.opened`, the occupancy metrics will be global.
+### Regional channel activity
+
+Seeing `channel.region.inactive` events in your [Dev Console](/docs/platform/account/app/console) logs is perfectly normal and expected behavior. Ably activates channels in different regions globally according to where clients are located and Ably's internal placement rules.
+
+A `channel.region.inactive` event simply indicates that a channel no longer has any clients in that specific region, or that the channel is shutting down altogether. This is part of Ably's normal operation to efficiently manage resources across its global infrastructure.
+
The following is an example of subscribing to all `[meta]channel.lifecycle` events:
From 16f05e9c04d235cbb21d09d9c3d474211a680e04 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:10:47 +0200
Subject: [PATCH 11/14] How to limit user numbers in channels
---
src/pages/docs/auth/capabilities.mdx | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/pages/docs/auth/capabilities.mdx b/src/pages/docs/auth/capabilities.mdx
index fd25089dc0..2867e1b6cf 100644
--- a/src/pages/docs/auth/capabilities.mdx
+++ b/src/pages/docs/auth/capabilities.mdx
@@ -49,6 +49,18 @@ The following capability operations are available for API keys and issued tokens
Although most capabilities need to be enabled for the resource you're using them with, there are exceptions. The `stats` permission only does something when attached to the wildcard resource `'*'`, or a resource that contains that as a subset, such as `'[*]*'`, since stats are app-wide.
+## Channel access control
+
+Ably does not provide the ability to set numeric limits on how many users can access a channel. However, you can control who has access to specific channels using token authentication and capabilities.
+
+Channel access is controlled through:
+
+* [Token authentication](/docs/auth/token) to restrict access by issuing tokens with specific capabilities to authorized users.
+* Specific `clientId` values in tokens to ensure only certain users can access particular channels.
+* Granting or restricting specific operations (`subscribe`, `publish`, `presence`) on channels through capability configurations.
+
+For implementing features like private messaging or group chats, design your channel naming strategy and use token authentication to ensure users only receive tokens with access to their relevant channels.
+
The `channel-metadata` permission works both ways. When associated with a specific channel or set of channels it allows you to [query the metadata of a channel](/docs/metadata-stats/metadata/rest) to request its status. When associated with the wildcard resource `'*'` it takes on an additional meaning: as well as allowing channel status requests for all channels, it also allows you to [enumerate all active channels](/docs/metadata-stats/metadata/rest#enumerate).
From 85822524237748e2305d164545eb5c6b6223a926 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:18:13 +0200
Subject: [PATCH 12/14] Can I make a History Call to several Channels
Simultaneously?
---
src/pages/docs/storage-history/history.mdx | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index 56371e35f7..92c19878b2 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -32,6 +32,17 @@ You can retrieve previously published messages using the history feature or usin
The Ably SDKs provide a straightforward API to retrieve paginated message event history. By default each page of history contains up to 100 messages and is ordered from most recent to oldest. You can retrieve channel history by using the [`history()`](/docs/api/realtime-sdk/history#channel-history) method.
+### Multi-channel history limitations
+
+History is stored per channel. You cannot retrieve history from multiple channels in a single call. Each history request must target a specific channel.
+
+To retrieve history from multiple channels, iterate over your channels and call the history method for each one. You can do this synchronously (one after another) or asynchronously (in parallel):
+
+* Synchronous: Call history for each channel sequentially. Simpler but slower.
+* Asynchronous: Make parallel history calls for better performance. Combine and sort results by timestamp if needed.
+
+This pattern is commonly used when implementing channel sharding, where related data is distributed across multiple channels.
+
The following example retrieves the latest message sent on a channel:
From 32d5bcfbeff6dd0fad0ae889cbc9e65464251f7e Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:29:57 +0200
Subject: [PATCH 13/14] How synchronized / up to date is channel history
betweendatacenters / regions?
---
src/pages/docs/storage-history/history.mdx | 36 ++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index 92c19878b2..7f042516d6 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -794,6 +794,42 @@ The following query parameters can be included in the `options` object when maki
| direction | forwards or backwards |
| limit | maximum number of messages to retrieve, up to 1,000 |
+## Global history synchronization
+
+Channel history is synchronized across all datacenters within approximately 100 milliseconds of message publication. This ensures consistent history retrieval regardless of which datacenter serves the request.
+
+### Synchronization process
+
+When a message is published, it moves through a multi-stage synchronization process.
+
+When a message is published:
+
+1. The message is immediately available in ephemeral storage (Redis) at the publishing datacenter.
+2. Within 100ms, the message propagates to persistent storage across multiple datacenters.
+3. History requests from any region will return the same message set once synchronization completes.
+
+### Storage consistency
+
+Different storage types have different consistency guarantees.
+
+The following table explains storage consistency levels:
+
+| Storage type | Synchronization time | Consistency level |
+|-------------|---------------------|------------------|
+| Ephemeral storage | Immediate at publishing datacenter | Eventually consistent globally |
+| Persistent storage | ~100ms across all datacenters | Strong consistency via quorum |
+| Message survivability | 99.999999% after acknowledgment | Replicated to 3+ regions |
+
+### Implications for applications
+
+Understanding these synchronization characteristics helps you design robust applications.
+
+* History requests may show slight delays (up to 100ms) for messages published in remote datacenters.
+* Connection recovery works reliably regardless of datacenter failover.
+* Message continuity is preserved even during regional failures.
+* Applications should not depend on instant global history consistency for real-time features.
+
+
## Ordering of historical messages
The order in which historical messages are returned with history is based on the message timestamp that was assigned by the channel in the region that the message was published in. This ordering is what Ably calls the canonical global order.
From fde6372c62676ca0bc799e7543357e20e3cc446b Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 30 Sep 2025 15:57:43 +0200
Subject: [PATCH 14/14] Tidy up commit
---
src/pages/docs/auth/capabilities.mdx | 4 +--
src/pages/docs/messages/index.mdx | 2 +-
.../metadata-stats/metadata/subscribe.mdx | 4 +--
.../docs/platform/account/app/console.mdx | 10 +++---
.../architecture/message-ordering.mdx | 8 ++---
src/pages/docs/platform/pricing/faqs.mdx | 32 ++++++++---------
.../docs/presence-occupancy/presence.mdx | 36 +++++++++----------
src/pages/docs/pub-sub/advanced.mdx | 24 ++++++-------
src/pages/docs/storage-history/history.mdx | 20 +++++------
src/pages/docs/storage-history/storage.mdx | 2 +-
10 files changed, 71 insertions(+), 71 deletions(-)
diff --git a/src/pages/docs/auth/capabilities.mdx b/src/pages/docs/auth/capabilities.mdx
index 2867e1b6cf..118fa989de 100644
--- a/src/pages/docs/auth/capabilities.mdx
+++ b/src/pages/docs/auth/capabilities.mdx
@@ -51,7 +51,7 @@ Although most capabilities need to be enabled for the resource you're using them
## Channel access control
-Ably does not provide the ability to set numeric limits on how many users can access a channel. However, you can control who has access to specific channels using token authentication and capabilities.
+Ably does not provide numeric limits on channel access. Control channel access using token authentication and capabilities.
Channel access is controlled through:
@@ -59,7 +59,7 @@ Channel access is controlled through:
* Specific `clientId` values in tokens to ensure only certain users can access particular channels.
* Granting or restricting specific operations (`subscribe`, `publish`, `presence`) on channels through capability configurations.
-For implementing features like private messaging or group chats, design your channel naming strategy and use token authentication to ensure users only receive tokens with access to their relevant channels.
+For private messaging or group chats, design channel naming strategies and use token authentication to ensure users receive tokens with access only to relevant channels.
The `channel-metadata` permission works both ways. When associated with a specific channel or set of channels it allows you to [query the metadata of a channel](/docs/metadata-stats/metadata/rest) to request its status. When associated with the wildcard resource `'*'` it takes on an additional meaning: as well as allowing channel status requests for all channels, it also allows you to [enumerate all active channels](/docs/metadata-stats/metadata/rest#enumerate).
diff --git a/src/pages/docs/messages/index.mdx b/src/pages/docs/messages/index.mdx
index 511c4f9306..9972bdbc55 100644
--- a/src/pages/docs/messages/index.mdx
+++ b/src/pages/docs/messages/index.mdx
@@ -36,7 +36,7 @@ The following are the properties of a message:
## Message delivery tracking
-You can ensure a message was successfully published by checking the [history](/docs/storage-history/history) of the channel for your message. However, it is only possible to check if a device has received a message from the device itself.
+Ensure a message was successfully published by checking the [history](/docs/storage-history/history) of the channel for your message. It is only possible to check if a device has received a message from the device itself.
Ably does not store per-message delivery logs, nor logs of who is subscribed to a channel at any point in time. This means it is not possible to check which users have received messages retroactively.
diff --git a/src/pages/docs/metadata-stats/metadata/subscribe.mdx b/src/pages/docs/metadata-stats/metadata/subscribe.mdx
index 38c93c895c..ea3184f7c7 100644
--- a/src/pages/docs/metadata-stats/metadata/subscribe.mdx
+++ b/src/pages/docs/metadata-stats/metadata/subscribe.mdx
@@ -58,9 +58,9 @@ The `data` property of all events is a [`ChannelDetails`](/docs/api/realtime-sdk
### Regional channel activity
-Seeing `channel.region.inactive` events in your [Dev Console](/docs/platform/account/app/console) logs is perfectly normal and expected behavior. Ably activates channels in different regions globally according to where clients are located and Ably's internal placement rules.
+Seeing `channel.region.inactive` events in your [Dev Console](/docs/platform/account/app/console) logs is normal behavior. Channels become active in different regions globally according to where clients are located and Ably's internal placement rules.
-A `channel.region.inactive` event simply indicates that a channel no longer has any clients in that specific region, or that the channel is shutting down altogether. This is part of Ably's normal operation to efficiently manage resources across its global infrastructure.
+A `channel.region.inactive` event indicates that a channel no longer has any clients in that specific region, or that the channel is shutting down altogether. This is part of Ably's normal operation to efficiently manage resources across its global infrastructure.
The following is an example of subscribing to all `[meta]channel.lifecycle` events:
diff --git a/src/pages/docs/platform/account/app/console.mdx b/src/pages/docs/platform/account/app/console.mdx
index 69a6ab7876..aec15bb350 100644
--- a/src/pages/docs/platform/account/app/console.mdx
+++ b/src/pages/docs/platform/account/app/console.mdx
@@ -28,13 +28,13 @@ The following explains the realtime monitoring tools in the application-wide eve
The dev console displays messages in realtime for debugging and testing purposes, but does not provide persistent message auditing or logging capabilities. Ably does not currently offer native functionality to view historical messages filtered by specific channels or client IDs for auditing purposes.
-If you need to audit or log messages by channel or client ID, you must implement this functionality on your application side. Consider using:
+If you need to audit or log messages by channel or client ID, implement this functionality on the application side. Consider using:
-- [Webhooks](/docs/platform/integrations/webhooks) to send message events to your logging system,
-- [Message queues](/docs/platform/integrations/queues) to process and store message data,
-- Client-side logging in your application code,
+- [Webhooks](/docs/platform/integrations/webhooks) to send message events to your logging system
+- [Message queues](/docs/platform/integrations/queues) to process and store message data
+- Client-side logging in your application code
-If you're interested in native message auditing features, please [contact support](mailto:support@ably.com) to discuss your requirements.
+For native message auditing features, [contact support](mailto:support@ably.com) to discuss requirements.
## Channels
diff --git a/src/pages/docs/platform/architecture/message-ordering.mdx b/src/pages/docs/platform/architecture/message-ordering.mdx
index 458799b0a5..ea178823be 100644
--- a/src/pages/docs/platform/architecture/message-ordering.mdx
+++ b/src/pages/docs/platform/architecture/message-ordering.mdx
@@ -61,15 +61,15 @@ To support both ordering systems efficiently, messages are stored with metadata
## Message ordering guarantees
-Ably ensures that messages are delivered to persistently connected subscribers on a channel in the order they were published when using the Realtime libraries. Each message sent on a realtime connection has a unique incrementing serial number, enabling this ordering guarantee.
+Ably delivers messages to persistently connected subscribers on a channel in the order they were published when using the Realtime libraries. Each message sent on a realtime connection has a unique incrementing serial number, enabling this ordering guarantee.
-For example, if you publish messages using a Realtime client library, subscribers on that channel anywhere in the world will receive those messages in the same order they were originally published.
+If you publish messages using a Realtime client library, subscribers on that channel anywhere in the world will receive those messages in the same order they were originally published.
### REST publishing considerations
When publishing using REST client libraries, Ably still guarantees that the order messages are received will be honoured for all subscribers. However, if you are sending messages at a high rate with separate REST requests, it is possible that a later HTTP request may reach Ably before a previous request due to variable network factors outside of Ably's control.
-If ordering is important to you when using REST, consider these approaches:
+If ordering is important when using REST, consider these approaches:
- Rate limit your HTTP requests
- [Batch messages](/docs/messages/batch) together in a single request
- Use Realtime client libraries to publish messages instead
@@ -78,6 +78,6 @@ If ordering is important to you when using REST, consider these approaches:
Ably supports connection state recovery, so even if a connection is lost and re-established, messages replayed when reconnected will be in sequential order.
-However, there is a rare situation where ordering may not be maintained: if a client is disconnected and the server maintaining that client's connection state is terminated or recycled during connection state recovery, messages may potentially be replayed out of order. This behaviour is by design to ensure that the backlog of messages held in the connection state do not prevent the new server from sending realtime messages to clients.
+However, there is a rare situation where ordering may not be maintained: if a client is disconnected and the server maintaining that client's connection state is terminated or recycled during connection state recovery, messages may potentially be replayed out of order. This behavior is by design to ensure that the backlog of messages held in the connection state do not prevent the new server from sending realtime messages to clients.
If message ordering is required in all cases, consider catching the connection disconnected event and either re-establishing a new connection or manually re-ordering incoming messages following disconnection.
diff --git a/src/pages/docs/platform/pricing/faqs.mdx b/src/pages/docs/platform/pricing/faqs.mdx
index a641669003..70b51c3305 100644
--- a/src/pages/docs/platform/pricing/faqs.mdx
+++ b/src/pages/docs/platform/pricing/faqs.mdx
@@ -13,9 +13,9 @@ FAQs related to package costs and package billing.
### When and how often am I billed?
-Your usage is calculated on the last day of the month. Invoices are issued in arrears on the 1st of the following month.
+Usage is calculated on the last day of the month. Invoices are issued in arrears on the 1st of the following month.
-If you upgraded in the middle of the month then the base package price will be charged pro-rata from the point in the month that you upgraded.
+Mid-month upgrades are charged pro-rata from the upgrade date.
Package downgrades take effect on the 1st of the following month.
@@ -27,11 +27,11 @@ FAQs related to pricing concepts and package limits.
### How do you count concurrent connections?
-The [limit](/docs/platform/pricing/limits#connection) on concurrent connections is for the maximum number of realtime clients [connected](/docs/connect) to Ably simultaneously at any point in time. HTTP requests such as those from REST clients do not count towards this number, as it is solely related to realtime connections.
+The [limit](/docs/platform/pricing/limits#connection) on concurrent connections is the maximum number of realtime clients [connected](/docs/connect) to Ably simultaneously at any point in time. HTTP requests from REST clients do not count towards this number.
### How do you count concurrent channels?
-The [limit](/docs/platform/pricing/limits#channel) on concurrent channels is for the maximum number of channels that are active simultaneously at any point in time.
+The [limit](/docs/platform/pricing/limits#channel) on concurrent channels is the maximum number of channels active simultaneously at any point in time.
[Channels](/docs/channels) are opened when either a message is published on the channel using a REST client, or a realtime client attaches to the channel. They are considered active until the channel is closed.
@@ -41,7 +41,7 @@ For example, if you have 10,000 users, and at your busiest time of the month the
### How is maximum message size measured?
-The maximum message size is based on the [package type](/docs/pricing#packages). The size is calculated as the sum of the `name`, `clientId`, `data` and `extras` [properties](/docs/api/realtime-sdk/messages#properties). This is before any compression or expansion occurs in the serialization process.
+Maximum message size is based on the [package type](/docs/pricing#packages). The size is calculated as the sum of the `name`, `clientId`, `data` and `extras` [properties](/docs/api/realtime-sdk/messages#properties) before any compression or expansion occurs in the serialization process.
* `name` and `clientId` are calculated as the size in bytes of their UTF-8 representation.
* `data` is calculated as the size in bytes if it is in binary, or its UTF-8 byte length if it is a string.
@@ -55,7 +55,7 @@ If [publishing](/docs/api/realtime-sdk/channels#publish) an array of messages, t
The effect of exceeding a [limit](/docs/platform/pricing/limits) differs depending on the limit.
-In most cases, if you exceed a limit then it means that your app is performing well. Ably won't penalize your success by blocking usage on your account for most limits. Normal service will continue to function, up to a point, beyond the limit. You would have received a notification of nearing a limit, and will receive another notification alerting you to the need to upgrade your package to accommodate your increased usage.
+In most cases, exceeding a limit means that an app is performing well. Ably won't penalize success by blocking usage on accounts for most limits. Normal service will continue to function, up to a point, beyond the limit. Notifications are sent when nearing a limit, and again when you need to upgrade your package to accommodate increased usage.
Limits will come into force either because there isn't any buffer on them, or because you have exceeded the buffer. Exceeding the limit depends on the limit:
@@ -76,15 +76,15 @@ Yes. [Enterprise packages](/docs/platform/pricing/enterprise) can include the ab
### Does Ably have a GDPR (General Data Protection Regulation) DPA (Data Processing Agreement) to sign?
-No. This is not necessary as Ably's online Terms incorporate everything that is required within a DPA document. This means that all customers globally can rely on Ably's [standard terms](https://www.ably.io/terms) which include the provisions for GDPR DPA, and which will apply automatically whenever they use AWS (Amazon Web Services) services to process personal data under the GDPR. By incorporating our GDPR DPA into the Ably Service Terms, we are simply extending the terms of our GDPR DPA to all customers globally who will require it under GDPR. Note that you can review the changes to our terms to incorporate GDPR requirements by reviewing our [legals audit trail](https://www.ably.io/legals) from 29 March 2018.
+No. This is not necessary as Ably's online Terms incorporate everything required within a DPA document. All customers globally can rely on Ably's [standard terms](https://www.ably.io/terms) which include the provisions for GDPR DPA, and which apply automatically when using AWS (Amazon Web Services) services to process personal data under the GDPR. By incorporating the GDPR DPA into the Ably Service Terms, the terms of the GDPR DPA extend to all customers globally who require it under GDPR. You can review the changes to the terms to incorporate GDPR requirements by reviewing the [legals audit trail](https://www.ably.io/legals) from 29 March 2018.
### Is Ably compliant with HIPAA (Health Insurance Portability and Accountability Act)?
-The following information outlines Ably's compliance with the US HIPAA Security Rule. If you require further information then [contact us](https://ably.com/contact) to find out more.
+The following information outlines Ably's compliance with the US HIPAA Security Rule. For further information, [contact us](https://ably.com/contact).
#### Is Ably a 'covered entity' which is required to comply with the HIPAA Security Rule?
-No. The HIPAA Security Rule operationalizes the protections contained in the HIPAA Privacy Rule by addressing the technical and non-technical safeguards that organizations called 'covered entities' must put in place to secure individuals' electronic protected health information (e-PHI).
+No. The HIPAA Security Rule operationalizes the protections contained in the HIPAA Privacy Rule by addressing the technical and non-technical safeguards that 'covered entities' must put in place to secure individuals' electronic protected health information (e-PHI).
Covered entities include Health Plans, Health Care Providers and Healthcare Clearinghouses.
@@ -110,7 +110,7 @@ Whilst Ably may be used by covered entities to transport individually identifiab
As a transport for information Ably does not know the nature of the data we are handling. It is possible for our customers, who may be covered entities under HIPAA, to transport individually identifiable health information. However, Ably cannot inspect that data so there is no access to protected health information and any such access would be incidental, if at all.
-Under HIPAA there are no restrictions on the use or disclosure of de-identified health information which neither identifies nor provides a reasonable basis to identify an individual. So where Ably customers, even covered entities, are using Ably only to transport de-identified health information, then HIPAA does not apply.
+Under HIPAA there are no restrictions on the use or disclosure of de-identified health information which neither identifies nor provides a reasonable basis to identify an individual. Where Ably customers, even covered entities, use Ably only to transport de-identified health information, HIPAA does not apply.
#### What level of data encryption does Ably use?
@@ -120,7 +120,7 @@ Ably also offers optional 256-bit AES symmetric encryption which makes it imposs
#### Where is data going through the Ably platform stored?
-Data in transit is stored ephemerally (i.e. not on disk) in all 16+ data centres [globally](https://www.ably.io/network). Each region can have two or more data centres.
+Data in transit is stored ephemerally (not on disk) in all 16+ data centres [globally](https://www.ably.io/network). Each region can have two or more data centres.
Messages are only persisted when history is [explicitly enabled](/docs/storage-history/storage), and that data is stored in US East Virginia, Europe Ireland, and Asia Singapore.
@@ -132,7 +132,7 @@ Under HIPAA, any covered entity must impose specified written safeguards on the
As per the points above Ably is neither a covered entity nor a business associate under the terms of the HIPAA Security Rule.
-However, some customers still like Ably to sign a business associate agreement which requires Ably to comply with specified safeguards.
+Some customers still like Ably to sign a business associate agreement which requires Ably to comply with specified safeguards.
In most cases, Ably is happy to do this as we have such safeguards in place as a matter of course and most business associate agreements are standard. We also recognize that an Ably customer, if a covered entity, may not contractually authorize Ably to make any use or disclosure of protected health information that would violate the Security Rule.
@@ -144,17 +144,17 @@ FAQs related to managing your Ably package.
### How does the Free package work?
-The [Free package](/docs/platform/pricing/free) is a zero friction way for you to explore Ably and its features without any commitment. No credit card is required to sign up and there's no time limit on how long you can try it for.
+The [Free package](/docs/platform/pricing/free) is a zero friction way to explore Ably and its features without any commitment. No credit card is required to sign up and there's no time limit.
-Once you need production-level scale then there's a simple path to upgrade to a subscription-based plan such as a [Standard](/docs/platform/pricing/standard) or [Pro](/docs/platform/pricing/pro) package.
+Once production-level scale is needed, there's a simple path to upgrade to a subscription-based plan such as a [Standard](/docs/platform/pricing/standard) or [Pro](/docs/platform/pricing/pro) package.
### Can I upgrade or downgrade my package at any time?
Yes.
1. Ensure you are the [account owner](/docs/platform/account/users).
-2. Log in to your [account](https://ably.com/login) and select **Billing** from the **Account** menu.
-3. Select the package that you would like to upgrade or downgrade to.
+2. Log in to your [account](https://ably.com/login) and select Billing from the Account menu.
+3. Select the package to upgrade or downgrade to.
Upgrades take effect immediately, whilst downgrades will take effect at the beginning of the following month.
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index c325ca8e37..d243656713 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -30,10 +30,10 @@ The following presence events are emitted:
| Event | Description |
|-------|-------------|
-| **Enter** | A new member has entered the channel |
-| **Leave** | A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present |
-| **Update** | An already present member has updated their [member data](#member-data). Being notified of member data updates can be very useful, for example, it can be used to update the status of a user when they are typing a message |
-| **Present** | When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered |
+| Enter | A new member has entered the channel |
+| Leave | A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present |
+| Update | An already present member has updated their [member data](#member-data). Being notified of member data updates can be useful, for example, it can be used to update the status of a user when they are typing a message |
+| Present | When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered |
### Member data
@@ -596,7 +596,7 @@ await channel.presence.enter();
The number of clients that can be simultaneously present on a channel is [limited](/docs/platform/pricing/limits#channel). This ensures the rate of presence messages remains supportable, as it is common for all members on a channel to change state at a similar time.
-Understanding presence message scaling is important because each presence update (enter, update, leave) is counted as a message for billing purposes, and presence follows an n-squared pattern when you have both subscribers and members present on a channel. This can lead to a surprisingly large number of messages being generated in a short time.
+Each presence update (enter, update, leave) is counted as a message for billing purposes, and presence follows an n-squared pattern when you have both subscribers and members present on a channel. This can lead to a surprisingly large number of messages being generated in a short time.
As an example, consider 200 clients subscribed to presence events on a channel and all of them join and leave the presence set within a few minutes. This would result in the following messages:
@@ -605,9 +605,9 @@ As an example, consider 200 clients subscribed to presence events on a channel a
* 200 presence messages published for the leave event.
* 200 × 200 (40,000) presence messages subscribed to for the leave events.
-This highlights the potential for 80,400 messages to be sent in a very short space of time on a single channel, which could result in rate limit issues or unexpected message costs.
+80,400 messages could be sent in a very short space of time on a single channel, which could result in rate limit issues or unexpected message costs.
-If your application needs to have all clients subscribed to presence messages then [enabling server-side batching](#server-side-batch) can reduce the number of events received by clients. If your application doesn't need to have all clients subscribed then you can set some of them to [be present without subscribing](#present-no-subscribe).
+If your application needs all clients subscribed to presence messages then [enabling server-side batching](#server-side-batch) can reduce the number of events received by clients. If your application doesn't need all clients subscribed then you can set some of them to [be present without subscribing](#present-no-subscribe).
### Server-side batching of presence events
@@ -617,7 +617,7 @@ If your application needs to have all clients subscribed to presence messages th
Enabling [server-side batching](/docs/messages/batch#server-side) for a channel means that Ably will group any messages that are published within a set period of time into batches. These batches are then delivered to subscribers as a single message.
-The interval over which this batching occurs is configurable. This is to ensure the trade-off between cost efficiency and user experience is appropriate, as a higher interval will increase the latency between message deliveries. This is usually less impactful to user experience for presence events than for regular messages.
+The interval over which this batching occurs is configurable. The trade-off between cost efficiency and user experience is appropriate, as a higher interval will increase the latency between message deliveries. This is usually less impactful to user experience for presence events than for regular messages.
Using server-side batching for presence events can reduce message costs when the membership of a channel is constantly changing. It also allows for a higher number of presence members per channel to be supported. By default, the number of presence members per channel is [limited](/docs/platform/pricing/limits#channel) to 200. With server-side batching enabled, this increases up to 20,000 clients depending on your [package type](/docs/pricing).
@@ -625,7 +625,7 @@ Using server-side batching for presence events can reduce message costs when the
Messages are streamed to clients as soon as they [attach](/docs/channels/states#attach) to a channel, as long as they have the [subscribe capability](/docs/auth/capabilities).
-Efficient use of capabilities can prevent clients from receiving presence events while still allowing them to enter the presence set. This is a common scenario when you want lots of people to be present without necessarily listening for presence change events, such as when you just want to know how many people are present on a channel at any point in time. This approach saves clients from subscribing to a potentially high volume of unnecessary messages.
+Efficient use of capabilities can prevent clients from receiving presence events while still allowing them to enter the presence set. This is a common scenario when you want lots of people to be present without necessarily listening for presence change events, such as when you just want to know how many people are present on a channel at any point in time.
One example of achieving this would be to use one channel for generic communications and another for the presence set. The following capabilities demonstrate this for clients and for servers:
@@ -666,11 +666,11 @@ Alternatively, [channel mode flags](/docs/channels/options#modes) can be used to
## External presence set maintenance
-While it's technically possible to use [webhooks](/docs/platform/integrations/webhooks) or other integration rules to maintain an external copy of a channel's presence set, this approach is generally not recommended for several important reasons:
+While it's technically possible to use [webhooks](/docs/platform/integrations/webhooks) or other integration rules to maintain an external copy of a channel's presence set, this approach is generally not recommended for several reasons:
### Implementation complexity
-Combining presence events into a presence set is not trivial to implement. Presence events are fundamentally a stream of delta updates to a CRDT (Conflict-free Replicated Data Type). These updates must be merged into your local copy following a detailed protocol designed to ensure:
+Combining presence events into a presence set is not trivial to implement. Presence events are fundamentally a stream of delta updates to a CRDT (Conflict-free Replicated Data Type). These updates must be merged into your local copy following a detailed protocol to ensure:
* Events can be applied in any order (commutative).
* Synthetic leave events are handled correctly.
@@ -691,13 +691,13 @@ External integration rules receive only a stream of updates with no way to retri
If you decide to proceed with webhook-based presence maintenance despite these limitations, you can use [`PresenceMessage.fromEncodedArray()`](/docs/api/realtime-sdk/presence#presence-from-encoded-array) to decode presence message arrays and translate numerical actions into readable strings. This method also handles data decoding and decryption if you're using [encryption](/docs/channels/options/encryption).
-For applications requiring an always-up-to-date presence set, we strongly recommend using Ably's realtime client libraries and attaching to the channel directly.
+For applications requiring an always-up-to-date presence set, use Ably's realtime client libraries and attaching to the channel directly.
## Retrieve presence members
The membership of the presence set can be retrieved by calling the [`get()`](/docs/api/realtime-sdk/presence#get) method on the `Presence` object of a channel. This returns an array of all members currently present on the channel and is available using the REST and realtime interfaces of an Ably SDK.
-An Ably client connected using the realtime interface of an SDK is responsible for keeping track of the presence set from the time that the channel is attached. An up to date presence set is pushed to the client following a channel attachment, and the presence set is updated on each subsequent presence event. This means that [`get()`](/docs/api/realtime-sdk/presence#get) returns the already known presence set retained in memory and does not trigger a new request to the Ably service.
+An Ably client connected using the realtime interface of an SDK is responsible for keeping track of the presence set from the time that the channel is attached. An up to date presence set is pushed to the client following a channel attachment, and the presence set is updated on each subsequent presence event. [`get()`](/docs/api/realtime-sdk/presence#get) returns the already known presence set retained in memory and does not trigger a new request to the Ably service.
The REST interface of an Ably SDK queries [the REST API](/docs/api/rest-api#presence) directly. No presence state is cached in the SDK itself.
@@ -901,11 +901,11 @@ if (presenceSet.isNotEmpty) {
A common requirement of the presence set is to keep an updated list of members that are currently present on a channel in your user interface.
-Many developers try to build the initial list using the [`get()`](/docs/api/realtime-sdk/presence#get) method and then mutate that list whenever a new presence event arrives. Ably advises against this approach, because it's easy to quickly go wrong and end up with a list that's out of sync with the real presence set.
+Many developers try to build the initial list using the [`get()`](/docs/api/realtime-sdk/presence#get) method and then mutate that list whenever a new presence event arrives. Don't use this approach because it's easy to quickly go wrong and end up with a list that's out of sync with the real presence set.
-One common error is to fail to take into account that a single `clientId` may be present multiple times on the same channel via different [connections](/docs/connect). As far as Ably is concerned, these are different members of the presence set as they have different `connectionId`s. For example, if a client with the ID “Sarah” is connected to a channel on both a desktop and a mobile device simultaneously, or via multiple tabs in a browser, “Sarah” will be present twice in the presence set with the same `clientID`. If "Sarah" leaves the channel on her mobile, your app sees the `leave` event and incorrectly removes her entry from the list.
+One common error is to fail to take into account that a single `clientId` may be present multiple times on the same channel via different [connections](/docs/connect). These are different members of the presence set as they have different `connectionId`s. For example, if a client with the ID "Sarah" is connected to a channel on both a desktop and a mobile device simultaneously, or via multiple tabs in a browser, "Sarah" will be present twice in the presence set with the same `clientID`. If "Sarah" leaves the channel on her mobile, your app sees the `leave` event and incorrectly removes her entry from the list.
-Ably recommends that you instead just `get()` the presence set afresh from the Ably SDK whenever you see a presence event and use it to rebuild the list of members in your user interface. The `get()` operation is free and a local call to the SDK and provides you the presence set that the client has already synced with the server. The server keeps the presence set up to date and there is not cost to using this approach.
+Instead just `get()` the presence set afresh from the Ably SDK whenever you see a presence event and use it to rebuild the list of members in your user interface. The `get()` operation is free and a local call to the SDK and provides the presence set that the client has already synced with the server. The server keeps the presence set up to date and there is no cost to using this approach.
The following is an example of calling the `get()` method on presence messages:
@@ -1268,7 +1268,7 @@ Note that the 15 second delay from being removed from the presence set is only f
<<<<<<< HEAD
### Browser tab close behavior
-When using the JavaScript client library (ably-js), you may notice that closing a browser tab results in a 15-second delay before presence members leave the presence set. By default, the client library closes the connection when the page is closed or refreshed, which should cause presence members to leave immediately. However, this may not occur in certain scenarios:
+When using the JavaScript client library (ably-js), you may notice that closing a browser tab results in a 15-second delay before presence members leave the presence set. By default, the client library closes the connection when the page is closed or refreshed, which should cause presence members to leave immediately. This may not occur in certain scenarios:
* If you have explicitly set the `closeOnUnload` client option to `false` (for example, when using connection state recovery), the connection won't close immediately on page unload. This option defaults to `true` in current versions.
* When using the `recover` client option and closing a tab (rather than refreshing), presence members remain for 15 seconds. The recover option stores the recovery key in the browser's localStorage to resume connections after page refresh, but cannot distinguish between tab close and refresh events.
@@ -1279,7 +1279,7 @@ When using the JavaScript client library (ably-js), you may notice that closing
>>>>>>> 6b5b1525c (fixup! Why don't presence members leave as soon as I close a tab?)
The time taken before a `leave` event is sent in the case of an abrupt disconnect can be reduced to a minimum of 1 second by setting a value for `remainPresentFor`, in milliseconds. This property is set within the `transportParams` property of the [`clientOptions`](/docs/api/realtime-sdk#client-options) object.
-It is important to note that it can initially take up to 30 seconds to identify that a client has been abruptly disconnected. Shorten the amount of time taken to identify abrupt disconnects using the [`heartbeatInterval`](/docs/connect#heartbeats) property if your app needs to quickly identify presence set members being abruptly disconnected.
+It can initially take up to 30 seconds to identify that a client has been abruptly disconnected. Shorten the amount of time taken to identify abrupt disconnects using the [`heartbeatInterval`](/docs/connect#heartbeats) property if your app needs to quickly identify presence set members being abruptly disconnected.
The following example code demonstrates establishing a connection to Ably with `remainPresentFor` set to 1 second:
diff --git a/src/pages/docs/pub-sub/advanced.mdx b/src/pages/docs/pub-sub/advanced.mdx
index 168c19edaa..54401627a9 100644
--- a/src/pages/docs/pub-sub/advanced.mdx
+++ b/src/pages/docs/pub-sub/advanced.mdx
@@ -3,11 +3,11 @@ title: Advanced pub-sub
meta_description: "Utilize advanced pub-sub features, such as, subscription filters and idempotent publishing."
---
-Once you've understood the [basics](/docs/pub-sub) of subscribing to a channel and publishing messages to it, you can explore the more advanced concepts and features. This can help you to build more complex and efficient applications.
+After understanding the [basics](/docs/pub-sub) of subscribing to a channel and publishing messages to it, explore the more advanced concepts and features to build more complex and efficient applications.
## Subscribing to channels
-There are more concepts to understand and more features you can utilize once you've explored the [basics of subscribing](/docs/pub-sub#subscribe) to channels.
+Explore additional concepts and features after understanding the [basics of subscribing](/docs/pub-sub#subscribe) to channels.
As a reminder, you can subscribe to all messages on a channel:
@@ -368,7 +368,7 @@ _ = channel.Publish(context.Background(), "action", "boom!")
```
-As subscribing to a channel implicitly attaches a client, it is important to understand that if a client subscribes to and then unsubscribes from a channel, the client remains attached. The client will continue to be sent published messages until they [`detach()`](/docs/api/realtime-sdk/channels#detach) from the channel.
+Subscribing to a channel implicitly attaches a client. If a client subscribes to and then unsubscribes from a channel, the client remains attached. The client will continue to be sent published messages until they [`detach()`](/docs/api/realtime-sdk/channels#detach) from the channel.
Any errors in attaching to a channel are received via the [`attach()`](/docs/api/realtime-sdk/channels#attach) callback. When attaching implicitly you can listen for [channel state changes](/docs/channels/states#attach) instead.
@@ -376,11 +376,11 @@ Any errors in attaching to a channel are received via the [`attach()`](/docs/api
### Detaching versus unsubscribing
-It is also important to understand the difference between between detaching and unsubscribing from a channel, and that messages will continue to be sent to clients if they only call the [`unsubscribe()`](/docs/api/realtime-sdk/channels#unsubscribe) method
+Understanding the difference between detaching and unsubscribing from a channel is essential. Messages will continue to be sent to clients if they only call the [`unsubscribe()`](/docs/api/realtime-sdk/channels#unsubscribe) method.
The [`detach()`](/docs/api/realtime-sdk/channels#detach) method detaches a client from a channel. A client will no longer receive any messages published to the channel once they detach. `unsubscribe()` only removes message listeners for a channel and is a client-side operation. To reiterate, Ably is unaware of whether or not a client has subscribed or unsubscribed from a channel. Messages will continue to be streamed to the client until `detach()` is called.
-As [`subscribe()`](/docs/api/realtime-sdk/channels#subscribe) implicitly attaches a client to a channel, be aware that if you call `subscribe()` followed by `unsubscribe()`, the client remains attached to the channel and will continue to be streamed messages from Ably.
+[`subscribe()`](/docs/api/realtime-sdk/channels#subscribe) implicitly attaches a client to a channel. If you call `subscribe()` followed by `unsubscribe()`, the client remains attached to the channel and will continue to be streamed messages from Ably.
### Server subscriptions
@@ -604,7 +604,7 @@ await channel.publish(message: messageData);
```
-Be aware that `message.extras.headers` must be a flat object. It can't contain any further nesting or arrays.
+`message.extras.headers` must be a flat object. It cannot contain any further nesting or arrays.
The following is an example of a filter expression subscribing to messages with the name "ice-cream", a flavor of "strawberry" and a cost of less than 50:
@@ -693,7 +693,7 @@ Clients require the subscribe [capability](/docs/auth/capabilities) for one of t
* `[*]`
* `[*]*`
-A client may also [attach](/docs/channels/states#attach) to the unfiltered instance of a channel for other operations, such as to subscribe to the [presence](/docs/presence-occupancy/presence) set. Be aware that if clients attach to the unfiltered instance, and have the subscribe capability for the channel itself, they will be sent all messages by Ably. This is because of the [difference between attaching and subscribing](#attach-subscribe) to a channel.
+A client may also [attach](/docs/channels/states#attach) to the unfiltered instance of a channel for other operations, such as to subscribe to the [presence](/docs/presence-occupancy/presence) set. If clients attach to the unfiltered instance and have the subscribe capability for the channel itself, they will be sent all messages by Ably due to the [difference between attaching and subscribing](#attach-subscribe) to a channel.
The following features are not supported using subscription filters:
@@ -704,7 +704,7 @@ The following features are not supported using subscription filters:
## Publish
-There are several more advanced concepts involved in publishing messages once you've understood the [basics of publishing](/docs/pub-sub#publish) messages.
+Several more advanced concepts are involved in publishing messages beyond the [basics of publishing](/docs/pub-sub#publish) messages.
As a reminder, to publish a message to a channel:
@@ -864,7 +864,7 @@ This property is only available using the realtime interface of an SDK, as it is
### Transient publishing
-Transient publishing is when a client publishes messages without attaching to a channel. This is a feature of the realtime interface of [certain Ably SDKs](/docs/sdks). Transient publishing can be beneficial if you intend to publish to many channels as it removes the need to attach to a channel each time you publish. It also avoids a client subscribing to messages which avoids messages being sent to it redundantly.
+Transient publishing is when a client publishes messages without attaching to a channel. This is a feature of the realtime interface of [certain Ably SDKs](/docs/sdks). Transient publishing can be beneficial when publishing to many channels as it removes the need to attach to a channel each time you publish. It also prevents a client from subscribing to messages, avoiding redundant message delivery.
The following is an example of publishing without attaching to a channel:
@@ -921,7 +921,7 @@ A message can be marked as *ephemeral* to exempt it from:
In other words, it will be exempt from everything except being delivered to currently-connected realtime connections.
-This is useful for events that are relevant only at the time they are published, and have no value when stale; examples might be streaming of continuously changing values such as realtime telemetry, position information, etc. Since ephemeral messages can be interspersed with other non-ephemeral messages on a channel, it is possible to use a single channel to convey all relevant events for some entity, including a mix of some that need to be persisted and others that are only ephemeral.
+Ephemeral messages are useful for events that are relevant only at the time they are published and have no value when stale; examples include streaming of continuously changing values such as realtime telemetry and position information. Since ephemeral messages can be interspersed with other non-ephemeral messages on a channel, you can use a single channel to convey all relevant events for an entity, including a mix that need to be persisted and others that are only ephemeral.
To mark a message as ephemeral, either include `ephemeral: true` in the message's extras object, or (for REST publishes) include `ephemeral: true` in the publish params.
@@ -949,12 +949,12 @@ Idempotency ensures that multiple publishes of the same message cannot result in
When idempotent publishing is enabled, the Ably SDK will internally assign a unique ID to each message which ensures that subsequent retry attempts cannot result in duplicate messages. Idempotent publishing is enabled by default in all latest Ably SDKs. It can be disabled by setting the `idempotentRestPublishing` [`ClientOptions`](/docs/api/rest-sdk#client-options) to `false`.
-In some cases you may wish to set the unique message ID yourself to achieve idempotency, such as:
+You may wish to set the unique message ID yourself to achieve idempotency in some cases, such as:
* To ensure idempotency when a publisher instance might be restarted, and continuous activity cannot be guaranteed.
* To integrate with an upstream system that uses message IDs, to ensure idempotency across an entire message processing pipeline.
-If setting your own message IDs be aware of the restrictions on its format when publishing messages atomically.
+When setting your own message IDs, note the restrictions on format when publishing messages atomically.
#### Client-specified message ID restrictions for multiple messages published atomically
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index 7f042516d6..3638fc3a2e 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -20,7 +20,7 @@ Two minutes of message history is available to retrieve by default. This can be
## History versus rewind
-You can retrieve previously published messages using the history feature or using the [rewind channel option](/docs/channels/options/rewind). There are several differences between the two features that are important to be aware of:
+You can retrieve previously published messages using the history feature or using the [rewind channel option](/docs/channels/options/rewind). Key differences between the two features:
* History can return up to 1000 messages in a single call, as a paginated list. Rewind returns at most 100 messages.
* The `history()` method can be called repeatedly with different parameters. Rewind only has an effect on an initial channel attachment.
@@ -43,7 +43,7 @@ To retrieve history from multiple channels, iterate over your channels and call
This pattern is commonly used when implementing channel sharding, where related data is distributed across multiple channels.
-The following example retrieves the latest message sent on a channel:
+This example retrieves the latest message sent on a channel:
```realtime_javascript
@@ -302,7 +302,7 @@ print('Last message: ${lastMessage.id} - ${lastMessage.data}');
### Channel history parameters
-The following query parameters can be included in the `options` object when making a call to `history()`. Note that `untilAttach` is only available when using the realtime interface of an Ably SDK:
+Query parameters for the `options` object when calling `history()`. Note that `untilAttach` is only available when using the realtime interface of an Ably SDK:
| Parameter | Description |
|-----------|-------------|
@@ -326,7 +326,7 @@ A `rewind` value that is a number (`n`) is a request to attach to the channel at
Note that this is only available with the realtime interface.
-The following example will subscribe to the channel and relay the last 3 messages:
+This example subscribes to the channel and relays the last 3 messages:
```realtime_javascript
@@ -420,13 +420,13 @@ channel.subscribe().listen((ably.Message message) {
```
-**Note**: You can also qualify a channel name with rewind when using the service without a library, such as with [SSE](/docs/protocols/sse) or [MQTT](/docs/protocols/mqtt).
+You can also qualify a channel name with rewind when using the service without a library, such as with [SSE](/docs/protocols/sse) or [MQTT](/docs/protocols/mqtt).
#### History with untilAttach
-It is possible to obtain message history that is continuous with the realtime messages received on an attached channel, in the backwards direction from the point of attachment. When a channel instance is attached, it's automatically populated by the Ably service with the serial number of the last published message on the channel. As such the serial number can be used to make a history request to the Ably service for all messages published before the channel was attached. Any new messages therefore are received in real time via the attached channel, and any historical messages are accessible via the history method.
+You can obtain message history that is continuous with the realtime messages received on an attached channel, in the backwards direction from the point of attachment. When a channel instance is attached, it's automatically populated by the Ably service with the serial number of the last published message on the channel. As such the serial number can be used to make a history request to the Ably service for all messages published before the channel was attached. Any new messages therefore are received in real time via the attached channel, and any historical messages are accessible via the history method.
-In order to benefit from this functionality, the `untilAttach` option can be used when making history requests on attached channels. If the channel is not yet attached, this will result in an error.
+Use the `untilAttach` option when making history requests on attached channels. If the channel is not yet attached, this will result in an error.
```realtime_javascript
@@ -571,7 +571,7 @@ print('Last message before attach: ${lastMessage.data}');
Retrieve [presence](/docs/presence-occupancy/presence) history using the [`history()`](/docs/api/realtime-sdk/presence#history) method on the presence object. This enables a client to retrieve historical presence events from the channel.
-The following example retrieves a paginated list of historical presence events published:
+This example retrieves a paginated list of historical presence events published:
```realtime_javascript
@@ -785,7 +785,7 @@ if (history.hasNext()) {
### Presence history parameters
-The following query parameters can be included in the `options` object when making a call to `presence.history()`:
+Query parameters for the `options` object when calling `presence.history()`:
| Parameter | Description |
|-----------|-------------|
@@ -834,7 +834,7 @@ Understanding these synchronization characteristics helps you design robust appl
The order in which historical messages are returned with history is based on the message timestamp that was assigned by the channel in the region that the message was published in. This ordering is what Ably calls the canonical global order.
-It is important to note that this is not necessarily the order that messages were received by a realtime client. The order in which each realtime client receives a message depends on which region the client is in.
+This is not necessarily the order that messages were received by a realtime client. The order in which each realtime client receives a message depends on which region the client is in.
Ably preserves ordering for a specific publisher on a specific channel but, for example, if two publishers in regions A and B publish _message-one_ and _message-two_ simultaneously, then it is very possible that a subscriber in region A will receive _message-one_ before _message-two_, but that a subscriber in region B will receive _message-two_ before _message-one_.
diff --git a/src/pages/docs/storage-history/storage.mdx b/src/pages/docs/storage-history/storage.mdx
index 5eaf4a970d..d9b3346f01 100644
--- a/src/pages/docs/storage-history/storage.mdx
+++ b/src/pages/docs/storage-history/storage.mdx
@@ -29,7 +29,7 @@ There is a cost associated with storing messages for longer than the minimum tim
### Message deletion
-Ably does not currently provide an API to delete persisted messages from the history. Once messages are stored with persisted history enabled, they will remain for the entire configured storage period. If you need to delete specific messages from history, please [contact us](https://ably.com/support) to discuss your requirements.
+Ably does not currently provide an API to delete persisted messages from the history. Once messages are stored with persisted history enabled, they remain for the entire configured storage period. If you need to delete specific messages from history, [contact us](https://ably.com/support) to discuss requirements.
Messages can be retrieved using the [history](/docs/storage-history/history) feature. This is illustrated in the following diagram: