From 89b18f6bb138f95b08fef5f0dbc2c88f8a012c79 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/22] 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 8ba78757b07a6d065f593b8ff1f322c9bc7ad15c 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/22] 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 2102407072f42819a0f3819bb92c6575f117562d 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/22] 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 6383ae039a142a3179801547f02005595ca67b8c Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 14:09:45 +0200
Subject: [PATCH 04/22] Am I able to check who has read published messages?
---
src/pages/docs/messages/index.mdx | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/pages/docs/messages/index.mdx b/src/pages/docs/messages/index.mdx
index 511c4f9306..53ed853fe3 100644
--- a/src/pages/docs/messages/index.mdx
+++ b/src/pages/docs/messages/index.mdx
@@ -40,6 +40,34 @@ You can ensure a message was successfully published by checking the [history](/d
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.
+### Read receipts and acknowledgments
+
+Ably does not natively support read receipts or message acknowledgments to track who has read published messages. This aligns with the fundamental pub-sub pattern where publishers are decoupled from subscribers and shouldn't need to know or care about the number of recipients.
+
+Since Ably doesn't control how many clients are expected on a channel, it's architecturally difficult to determine who has read or received a message. However, you can implement a custom read receipt system by having subscribers publish acknowledgment messages when they consider a message received or read:
+
+```javascript
+// Subscriber acknowledges reading a message
+channel.subscribe('content', (message) => {
+ // Process the message
+ console.log('Received:', message.data);
+
+ // Send read receipt
+ channel.publish('read-receipt', {
+ messageId: message.id,
+ readBy: clientId,
+ readAt: Date.now()
+ });
+});
+
+// Publisher listens for read receipts
+channel.subscribe('read-receipt', (receipt) => {
+ console.log(`Message ${receipt.data.messageId} read by ${receipt.data.readBy}`);
+});
+```
+
+This approach requires application-level implementation and careful consideration of scalability in high-traffic scenarios.
+
## 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 10e5e3cef88e14568a9265b0e53e56d3df352d32 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 05/22] 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..af9e719aa0 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.
+
+#### Restrictions for atomic publishing
+
+When publishing multiple messages atomically (e.g., by calling `publish()` with an array of messages), those messages are bundled together and will either all succeed or all fail. For idempotency purposes, there is only a single idempotency key for the entire array of messages.
+
+To prevent confusion about individual message idempotency, Ably requires that all messages in an atomic publish must have IDs 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 atomically with a base ID of `foo`, the messages must have IDs `foo:0`, `foo:1`, and `foo:2`. This emphasizes that the messages share a single idempotency key (`foo`).
+
+```javascript
+// Correct: atomic publishing with derived IDs
+await channel.publish([
+ { id: 'foo:0', data: 'first' },
+ { id: 'foo:1', data: 'second' },
+ { id: 'foo:2', data: 'third' }
+]);
+
+// Incorrect: would be rejected
+await channel.publish([
+ { id: 'foo', data: 'first' },
+ { id: 'bar', data: 'second' } // Different base ID not allowed
+]);
+```
+
+If you want messages to be individually idempotent, publish them separately using multiple `publish()` calls, or use the [batch publishing API](/docs/messages/batch#batch-publish) where each message is contained in its own batch specification.
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 a74ea8e6402f35a0bbf467d43f460551b1642ae1 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 14:48:23 +0200
Subject: [PATCH 06/22] I'm not receiving messages, how do I debug that?
---
src/pages/docs/pub-sub/advanced.mdx | 75 +++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/src/pages/docs/pub-sub/advanced.mdx b/src/pages/docs/pub-sub/advanced.mdx
index af9e719aa0..3bd17068c4 100644
--- a/src/pages/docs/pub-sub/advanced.mdx
+++ b/src/pages/docs/pub-sub/advanced.mdx
@@ -981,6 +981,81 @@ await channel.publish([
If you want messages to be individually idempotent, publish them separately using multiple `publish()` calls, or use the [batch publishing API](/docs/messages/batch#batch-publish) where each message is contained in its own batch specification.
+## Troubleshooting message delivery
+
+If you're not receiving messages, follow these systematic debugging steps to identify the issue:
+
+### Common causes
+
+Ably channel names are case-sensitive. `sports` is different from `Sports`, and `my_channel` is different from `"my_channel"`. If using protocol adapters like Pusher or Laravel Broadcasting, channel mappings may differ (`private:my_private_channel` vs `private-my_private_channel`).
+
+Ensure you're using the same Ably app everywhere. Don't publish with an API key from your Sandbox app while subscribing with an API key from your Production app.
+
+### Isolate the problem
+
+Use the [Ably dashboard](https://ably.com/dashboard) dev console to determine if the issue is on the publishing or subscribing side:
+
+1. Open the dev console and attach to the channel you're publishing on.
+2. Try publishing a message.
+3. If the message appears in the dev console but not on your subscribing client, the problem is on the subscribing side.
+4. If the message doesn't appear in the dev console, the problem is on the publishing side.
+
+### Enable verbose logging
+
+Enable client verbose logs on both publisher and subscriber to examine protocol-level messages:
+
+```javascript
+const realtime = new Ably.Realtime({
+ key: 'your-api-key',
+ log: { level: 4 } // Enable verbose logging
+});
+```
+
+Check the logs for:
+- Publisher: Protocol send message events
+- Subscriber: Protocol receive message events
+
+### Debugging publish problems
+
+When calling `channel.publish()`, use a callback to detect failures:
+
+```javascript
+channel.publish('event', 'data', (err) => {
+ if (err) {
+ console.error('Publish failed:', err.message);
+ } else {
+ console.log('Publish successful');
+ }
+});
+```
+
+### Debugging subscribe problems
+
+Check these common issues:
+
+Ensure you're connected to Ably:
+
+```javascript
+console.log('Connection state:', realtime.connection.state);
+// Should be 'connected'
+```
+
+Ensure you're attached to the channel:
+
+```javascript
+console.log('Channel state:', channel.state);
+// Should be 'attached'
+```
+
+Verify you've added a subscribe listener and that event names match exactly:
+
+```javascript
+// Make sure this matches your publish event name exactly
+channel.subscribe('specific-event', (message) => {
+ console.log('Received:', message.data);
+});
+```
+
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 6760f8b67220b22a7ee7b235aa50e0c8bc82e509 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 07/22] 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 86cf208642cce18c1a208881dfba73505055c9e5 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 15:14:11 +0200
Subject: [PATCH 08/22] How does Ably count and bill for messages?
---
src/pages/docs/platform/pricing/faqs.mdx | 58 ++++++++++++++++++++++--
1 file changed, 54 insertions(+), 4 deletions(-)
diff --git a/src/pages/docs/platform/pricing/faqs.mdx b/src/pages/docs/platform/pricing/faqs.mdx
index a641669003..d7a7828453 100644
--- a/src/pages/docs/platform/pricing/faqs.mdx
+++ b/src/pages/docs/platform/pricing/faqs.mdx
@@ -51,6 +51,56 @@ If the `data` is binary and the message is sent on a text transport with base64
If [publishing](/docs/api/realtime-sdk/channels#publish) an array of messages, the message size limit applies to the sum of all messages in the array, as they are treated as a single unit by the Ably platform. This is even though the are published and broadcast atomically.
+### How does Ably count and bill for messages?
+
+Ably accounts are priced based on the number of messages, total bandwidth used, and concurrent connections within any calendar month. Messages are billed in 2KiB chunks, so if you send a 50KiB message to a channel with 100 subscribers, this counts as 25 inbound messages and 2,500 outbound messages.
+
+#### Message counting examples
+
+The following table illustrates how messages are counted for billing purposes:
+
+| Scenario | Inbound messages | Outbound messages | Total messages |
+|------------------------------------------------|------------------|------------------|---------------|
+| Publish 50KiB message to 1 channel, 100 subs | 25 | 2,500 | 2,525 |
+| Publish 2KiB message to 1 channel, 10 subs | 1 | 10 | 11 |
+| Presence event (enter) with 5 attached clients | 1 | 5 | 6 |
+
+#### Publish and subscribe counting
+
+The following table explains how messages are counted for publish and subscribe actions:
+
+| Action | Message count description |
+|-------------------------|------------------------------------------------------------------------------------------|
+| Publishing | Every message published on a channel counts as one message. |
+| Subscribing | Every message received on a channel counts as one message. |
+| Client-side filtering | Has no effect on counting - all messages published to a channel are received by clients. |
+| Echo messages | If [echo messages](/docs/api/realtime-sdk/types#client-options) is enabled (default), published messages are echoed back to the publisher, counting as both published and received. |
+| Subscribe capability | Clients without `subscribe` [capability](/docs/auth/capabilities) will not receive (or be billed for) messages. |
+
+#### Presence message counting
+
+| Event type | Counting rule |
+|--------------------|-----------------------------------------------------------------------------------------------|
+| Presence events | Each presence event (enter, update, leave) counts as one published message. |
+| Presence reception | Each client attached to the channel receives the presence event as one message. |
+| Echo behavior | Presence messages are always echoed to the originating client (unlike regular messages where this can be disabled). |
+| Own presence | Clients always receive their own presence messages regardless of subscribe capability. |
+
+#### Other message types
+
+The following table explains how other message types are counted for billing purposes:
+
+| Message type | Counting rule |
+|--------------------------|----------------------------------------------------------------------------------------------|
+| History | Each message stored and each message retrieved from [history](/docs/storage-history/history) counts as one message |
+| Push notifications | Each push notification delivered counts as one message |
+| Integrations | Messages sent to [webhooks](/docs/platform/integrations/webhooks), [queues](/docs/platform/integrations/queues), or other integrations count as additional messages |
+| Metadata and lifecycle | [Channel lifecycle events](/docs/metadata-stats/metadata) and [statistics](/docs/metadata-stats/stats) subscription messages count toward your limit |
+
+#### Bandwidth calculation
+
+Messages over 2KiB count as multiple messages for billing. For example, a 16KiB message counts as 8 messages when published, and if received by 10 clients, counts as 80 total messages. The bandwidth allocation is calculated as: total monthly messages × average message size allowance (typically 2KiB).
+
### What happens if I exceed a limit?
The effect of exceeding a [limit](/docs/platform/pricing/limits) differs depending on the limit.
@@ -104,11 +154,11 @@ No. A business associate is “a person or organization, other than a member of
Ably could be a business associate under this definition, however, the security rule further defines business associates as “organizations are not considered business associates if their functions or services do not involve the use or disclosure of protected health information, and **where any access to protected health information by such persons would be incidental, if at all**.”
-Whilst Ably may be used by covered entities to transport individually identifiable health information, Ably does not inspect the data it transports. Ably never inspects payloads. We treat them as opaque. Ably is a conduit for data (a 'dumb pipe') like the postal service in the physical world.
+Whilst Ably may be used by covered entities to transport individually identifiable health information, Ably does not inspect the data it transports. Ably never inspects payloads. Ably treats them as opaque. Ably is a conduit for data (a 'dumb pipe') like the postal service in the physical world.
#### Does Ably transport individually identifiable health information?
-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.
+As a transport for information Ably does not know the nature of the data being handled. 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.
@@ -116,7 +166,7 @@ Under HIPAA there are no restrictions on the use or disclosure of de-identified
Ably uses TLS 2048 bit encryption for all data in transit. However, customers can elect not to transmit their data over TLS. All data within the same data centre in Ably is moved around unencrypted as it cannot be intercepted, but is always encrypted when moved between data centres.
-Ably also offers optional 256-bit AES symmetric encryption which makes it impossible for Ably to inspect any data payloads moving through the system at all, even if we wanted to.
+Ably also offers optional 256-bit AES symmetric encryption which makes it impossible for Ably to inspect any data payloads moving through the system.
#### Where is data going through the Ably platform stored?
@@ -134,7 +184,7 @@ As per the points above Ably is neither a covered entity nor a business associat
However, 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.
+In most cases, Ably is happy to do this as Ably has such safeguards in place as a matter of course and most business associate agreements are standard. Ably also recognizes 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.
Ably is willing to sign Business Associate Agreements for our [Enterprise package](/docs/platform/pricing/enterprise) customers. [Contact us](https://ably.com/contact) if you have a Business Associate Agreement you would like Ably to review.
From bea98300c47a3ae3f7ca2ac1771d0d8b8c6caca7 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 15:24:21 +0200
Subject: [PATCH 09/22] How can I reduce my message count?
---
src/pages/docs/platform/pricing/faqs.mdx | 71 +++++++++++++++++++-----
1 file changed, 57 insertions(+), 14 deletions(-)
diff --git a/src/pages/docs/platform/pricing/faqs.mdx b/src/pages/docs/platform/pricing/faqs.mdx
index d7a7828453..6138c01ed4 100644
--- a/src/pages/docs/platform/pricing/faqs.mdx
+++ b/src/pages/docs/platform/pricing/faqs.mdx
@@ -43,9 +43,9 @@ For example, if you have 10,000 users, and at your busiest time of the month the
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.
-* `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.
-* `extras` is calculated as the string length of its JSON representation.
+- `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.
+- `extras` is calculated as the string length of its JSON representation.
If the `data` is binary and the message is sent on a text transport with base64 encoded data, and has an encoding attribute of base64, the size is calculated using the actual size of the binary data, not its base64 encoded string.
@@ -101,22 +101,65 @@ The following table explains how other message types are counted for billing pur
Messages over 2KiB count as multiple messages for billing. For example, a 16KiB message counts as 8 messages when published, and if received by 10 clients, counts as 80 total messages. The bandwidth allocation is calculated as: total monthly messages × average message size allowance (typically 2KiB).
+### How can I reduce my message count?
+
+First, understand how messages are counted by reading the [message counting section](#message-counting) above. Then use these strategies to optimize your usage:
+
+#### Analyze your usage with statistics API
+
+Use the [Statistics API](/docs/metadata-stats/stats) to get a detailed breakdown of message usage by type (REST/realtime publishes, realtime receives, history, presence, integrations) by minute, hour, day, or month. This will identify which areas consume the most messages.
+
+#### Optimize realtime messages
+
+If outbound realtime messages dominate your usage:
+
+| Optimization strategy | Description |
+|----------------------|-------------|
+| Use capabilities | For clients that only need to publish (not subscribe), use API keys or tokens without the `subscribe` [capability](/docs/auth/capabilities). This prevents them from receiving messages and being billed for them. |
+| Disable echo messages | If clients don't need to receive their own published messages, set the [`echoMessages`](/docs/api/realtime-sdk/types#client-options) client option to `false`. |
+| Avoid unnecessary messages | Ensure messages are only published when clients need them. |
+| Remember client-side filtering limitations | Client-side filtering has no effect on message counting. All messages published to a channel are received by every attached client (unless they lack `subscribe` capability). |
+
+#### Optimize presence usage
+
+Presence message usage scales approximately as the square of attached clients, since each client broadcasts state changes to every other client:
+
+| Optimization strategy | Description |
+|----------------------|-------------|
+| Evaluate presence necessity | Consider whether all clients need full presence functionality |
+| Use occupancy for counts | Use [occupancy](/docs/presence-occupancy/occupancy) instead of presence for simple user counts |
+| Limit presence capability | Limit the number of clients with presence capability on high-traffic channels |
+
+#### Optimize history usage
+
+| History type | Optimization strategy | Description |
+|--------------|----------------------|-------------|
+| Storage | Disable unnecessary persistence | Each stored message counts as one message. Disable [persistence](/docs/storage-history/storage) on channel namespaces where history is not needed. |
+| Retrieval | Use limit parameters | Each retrieved message counts as one message. Use the `limit` parameter to retrieve only the messages needed (e.g., most recent 5 messages) rather than retrieving more and discarding them. |
+
+#### Optimize integrations
+
+| Integration type | Optimization strategy | Description |
+|------------------|----------------------|-------------|
+| Reactor rules | Disable unused rules | Each message sent to [webhooks](/docs/platform/integrations/webhooks), [queues](/docs/platform/integrations/queues), or other integrations counts as an additional message. Disable unused rules in your dashboard. |
+| Active integrations | Review necessity | Ensure all active integrations are necessary for your application. |
+
### What happens if I exceed a limit?
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.
-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:
+Limits will come into force either because there is not any buffer on them, or because you have exceeded the buffer. Exceeding the limit depends on the limit:
-* **Count-based limits**: only a set amount of resources can be in use simultaneously. This means that any usage in excess of that limit will be restricted until existing resources are removed, such as the number of concurrent connections, or concurrent channels.
-* **Rate-based limits**: the rate at which resources can be used, expressed as their frequency per second. Usage in excess of a rate limit can be rejected locally, such as a publisher trying to publish at a rate in excess of the publish rate per channel. The publisher will have any publish attempts in excess of this rate rejected and have an error code returned. Other rate limits apply rate suppression across your account when exceeded. For example, if the limit for publishing messages into a queue is exceeded by twice the rate, then each message will have a 50% chance of being rejected. The suppression probability is continuously updated based on the publishing rate.
+- **Count-based limits**: Only a set amount of resources can be in use simultaneously. This means that any usage in excess of that limit will be restricted until existing resources are removed, such as the number of concurrent connections, or concurrent channels.
+- **Rate-based limits**: The rate at which resources can be used, expressed as their frequency per second. Usage in excess of a rate limit can be rejected locally, such as a publisher trying to publish at a rate in excess of the publish rate per channel. The publisher will have any publish attempts in excess of this rate rejected and have an error code returned. Other rate limits apply rate suppression across your account when exceeded. For example, if the limit for publishing messages into a queue is exceeded by twice the rate, then each message will have a 50% chance of being rejected. The suppression probability is continuously updated based on the publishing rate.
## Data and compliance
FAQs related to data and compliance.
-### What is your Data Protection policy?
+### What is your data protection policy?
Please read Ably's [Data Protection Policy](https://ably.com/data-protection) for full details on how your data is handled.
@@ -132,7 +175,7 @@ No. This is not necessary as Ably's online Terms incorporate everything that is
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.
-#### Is Ably a 'covered entity' which is required to comply with the HIPAA Security Rule?
+#### 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).
@@ -148,7 +191,7 @@ Ably is not a Health Plan, nor a Health Care Provider, nor a Clearinghouse as de
*Health care clearinghouses include billing services, repricing companies, community health management information systems, and value-added networks and switches **if these entities perform clearinghouse functions**.”*
-#### Is Ably a 'business associate' under the terms of the HIPAA Security Rule?
+#### Is Ably a business associate under the terms of the HIPAA Security Rule?
No. A business associate is “a person or organization, other than a member of a covered entity's workforce, that performs certain functions or activities on behalf of, or provides certain services to, a covered entity that involve the use or disclosure of individually identifiable health information.”
@@ -158,7 +201,7 @@ Whilst Ably may be used by covered entities to transport individually identifiab
#### Does Ably transport individually identifiable health information?
-As a transport for information Ably does not know the nature of the data being handled. 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.
+As a transport for information Ably does not know the nature of the data being handled. It is possible for Ably 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.
@@ -176,7 +219,7 @@ Messages are only persisted when history is [explicitly enabled](/docs/storage-h
[Enterprise packages](/docs/platform/pricing/enterprise) have the option to choose where the data is routed and stored geographically.
-#### Is Ably prepared to sign a 'Business Associate Agreement' (BAA)?
+#### Is Ably prepared to sign a Business Associate Agreement (BAA)?
Under HIPAA, any covered entity must impose specified written safeguards on the individually identifiable health information used or disclosed by its business associates.
@@ -192,7 +235,7 @@ Ably is willing to sign Business Associate Agreements for our [Enterprise packag
FAQs related to managing your Ably package.
-### How does the Free package work?
+### 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.
@@ -200,13 +243,13 @@ Once you need production-level scale then there's a simple path to upgrade to a
### Can I upgrade or downgrade my package at any time?
-Yes.
+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.
-Upgrades take effect immediately, whilst downgrades will take effect at the beginning of the following month.
+Upgrades take effect immediately, while downgrades will take effect at the beginning of the following month.
### Can I close my account at any time?
From da77b5818321c66a0b30b69fd8a4ea41d1a78311 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 18:30:27 +0200
Subject: [PATCH 10/22] Is there a maximum number of channels per connection?
---
src/pages/docs/platform/errors/codes.mdx | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/pages/docs/platform/errors/codes.mdx b/src/pages/docs/platform/errors/codes.mdx
index d820806a99..e6e9e4a603 100644
--- a/src/pages/docs/platform/errors/codes.mdx
+++ b/src/pages/docs/platform/errors/codes.mdx
@@ -672,9 +672,25 @@ Note: In older versions of the ably-java SDK, this error was incorrectly assigne
## 90010: Maximum number of channels per connection exceeded
-This error occurs when a Realtime client [attaches](/docs/channels/states#attach) to more channels than the account allows on a single connection. This happens when channels are attached but never explicitly detached, causing the limit to be reached.
+This error occurs when a Realtime client [attaches](/docs/channels/states#attach) to more channels than the account allows on a single connection. The error message appears as:
-**Resolution:** Review your channel [limits](/docs/platform/pricing/limits#channel) and ensure that channels are explicitly detached when no longer needed.
+```
+[ErrorInfo: Maximum number of channels per connection MAXCHANNELS exceeded; statusCode=90010; code=400]
+```
+
+Where `MAXCHANNELS` is your account's maximum number of channels per connection limit.
+
+This limit exists to ensure that connection state and the load imposed for connection resumption don't become unbounded. With more channels on a single connection, the size of the connection state grows, as does the work required to resume or recover the connection if dropped.
+
+**Resolution:** Use one or more of the following strategies to avoid reaching this limit:
+
+* For SDKs that support transient publishes (ably-js >= 1.0.9, ably-ruby >= 1.1, ably-java >= 1.0.10), call `channel.publish()` without attaching to avoid the channel limit altogether. This is suitable when you only need to publish messages, not receive them.
+* Call [`channel.detach()`](/docs/channels/states#detach) once finished with a channel to free up the connection slot. This is suitable when using channels for short periods of time.
+* REST publishes are stateless and don't attach to channels. This is suitable when publish rates are low or transient publishes aren't supported.
+* Distribute channels across multiple realtime connections. This is suitable when you genuinely need hundreds of simultaneous channel attachments.
+* Contact Ably to configure higher limits for applications requiring very high channel counts per connection.
+
+Remember that subscribing to a channel (and publishing in SDKs without transient publishing support) will implicitly attach to it, so detaching after these operations helps keep your channel count manageable.
## 90021: Max channel creation rate exceeded
From fab7f54f745f2a8f5cbf7b922914b2557215e2a0 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Tue, 23 Sep 2025 18:38:16 +0200
Subject: [PATCH 11/22] Can I attach and subscribe to channels using wildcards?
---
src/pages/docs/channels/index.mdx | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/src/pages/docs/channels/index.mdx b/src/pages/docs/channels/index.mdx
index d326c89279..3063167188 100644
--- a/src/pages/docs/channels/index.mdx
+++ b/src/pages/docs/channels/index.mdx
@@ -36,6 +36,26 @@ With [basic pub-sub](/docs/pub-sub) you create a channel, subscribe to it, and t
To get started with implementing any feature, a client must first create or retrieve an instance of a channel. A channel is created, or an existing channel is retrieved from the `Channels` collection. You can only connect to one channel in a single operation.
+### Wildcard channel subscriptions
+
+Wildcard channel subscriptions are not supported when attaching to channels. For example, the following operations are not possible:
+
+```javascript
+// This will NOT work - wildcard subscriptions are not supported
+var channel = ably.channels.get('foo:*');
+channel.attach(); // Attempting to attach to all channels matching foo:* will fail
+```
+
+Wildcard subscriptions are not supported for several technical and architectural reasons:
+
+* Scalability limitations: attaching to an unbounded number of channels in a single operation does not scale for client devices or Ably's servers terminating those connections.
+* Distributed architecture: Channels in Ably's cluster are dynamically distributed across available resources and move frequently. Each channel is autonomous to ensure the system remains reliable without a single point of failure. Supporting wildcard subscriptions would require maintaining connections to every server that could possibly run a matching channel, which does not scale.
+* Data delivery guarantees: - Wildcard subscriptions make it impossible to offer the data delivery guarantees and quality of service that Ably provides because:
+ * There's no deterministic way to know which channels a client is actually attached to at any point in time.
+ * If a client device becomes overloaded or exceeds rate limits, servers would need to selectively drop messages across random channels, making it unclear which messages were missed.
+
+Because Ably connections are multiplexed, you can attach and detach from channels dynamically over the same connection. This enables you to effectively implement wildcard-like functionality by programmatically managing channel subscriptions as needed.
+
Channels are identified by their unique name. The following restrictions apply to when naming a channel:
* Channel names are case sensitive
@@ -61,11 +81,11 @@ Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}");
```
```realtime_csharp
-IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); realtime
+IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}");
```
```realtime_ruby
-channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') realtime
+channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}')
```
```realtime_python
@@ -101,11 +121,11 @@ Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}");
```
```rest_csharp
-Channel channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); rest
+Channel channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}");
```
```rest_ruby
-channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') rest
+channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}')
```
```rest_python
From 1143cbac2edb0d5435328e2563854f81024cb122 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 12/22] 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 6b2cc1ecdb..bcc1040bfb 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 981c2ca583a6cbd286af1eb03d4034d6f3598e42 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 13/22] 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 bcc1040bfb..d29b5c1cbf 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 bea5c08eea3ceef13f1f7660fab954801e4da263 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 14/22] Why don't presence members leave as soon as I close a
tab?
---
src/pages/docs/presence-occupancy/presence.mdx | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index d29b5c1cbf..ee6d0a853b 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -1263,6 +1263,15 @@ 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.
+### 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.
+
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.
From 2a5ca912b68184d19975b9138ee7cf05d2e3b47e 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 15/22] 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 ee6d0a853b..fe7899cc20 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 ec7d36987cadbd3f6ddd4ab31e65ca82ff208375 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:00:16 +0200
Subject: [PATCH 16/22] Why do you have a limit on the number of members
present on a channel?
---
.../docs/presence-occupancy/presence.mdx | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/src/pages/docs/presence-occupancy/presence.mdx b/src/pages/docs/presence-occupancy/presence.mdx
index fe7899cc20..c5ab6997ec 100644
--- a/src/pages/docs/presence-occupancy/presence.mdx
+++ b/src/pages/docs/presence-occupancy/presence.mdx
@@ -621,6 +621,36 @@ The interval over which this batching occurs is configurable. This is to ensure
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).
+## Why presence member limits exist
+
+It's important to understand that there are no limits on the number of clients that can [attach](/docs/channels/states#attach) and [subscribe](/docs/pub-sub) to a channel for regular messaging. Presence limits apply specifically to the presence feature only.
+
+The limits on presence members are largely not driven by constraints within the Ably platform, but instead exist as a precaution for customers who want very large presence sets and may not realize the implications of this approach.
+
+### Presence scaling implications
+
+To understand why limits exist, consider what happens when members enter or leave presence on a channel:
+
+* Only one enter event is emitted back to the user (2 total messages: 1 publish, 1 subscribe)
+* Two users receive the event (3 total messages: 1 publish, 2 subscribe)
+* Three users receive the event (4 total messages: 1 publish, 3 subscribe)
+
+Following this pattern, if 1,000 users enter a channel:
+* 1,000 enter events would be published.
+* 501,000 enter events would be received.
+* If all users then left: 1,000 leave events published + 501,000 leave events received.
+* Total: Over 1 million events for 1,000 members entering and leaving.
+
+If all 1,000 users left over 10 seconds, messages would be published at a rate of **50,000 per second**, which could impact performance and incur significant costs.
+
+### Managing large presence sets
+
+When considering channels with many present members, evaluate these factors:
+
+* Do all present members need to subscribe to presence events? If not, the scaling problem is eliminated (1,000 users entering/leaving generates only 2,000 events). Remove the `subscribe` capability for users who don't need presence notifications using token authentication.
+* Can you handle the message volume? If your application can consume this many messages, contact Ably to discuss increasing presence member limits for your account.
+* Alternative approaches: Use the REST API to periodically get presence members or have only server-side components subscribe to presence events.
+
### Be present without subscribing to presence events
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).
From be2a85229f1592f1d63fa9cb481273e687cae836 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 17/22] 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..f1cf9c6a25 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.
+### Understanding regional channel activity
+
+Seeing `channel.region.inactive` events in your [Dev Console](/docs/platform/account/app/console) logs is perfectly normal and expected behavior. When channels become active, they are activated 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 585317da988022e3044f6736e9b5e4cf33390dba 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 18/22] 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..1f90258282 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:
+
+* Use [token authentication](/docs/auth/token) to restrict access to channels by issuing tokens with specific capabilities only to authorized users.
+* Set specific `clientId` values in tokens to ensure only certain users can access particular channels.
+* Grant or restrict 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 40bfd1ee594a34b613f60218f22ea673d3931a88 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 19/22] 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..4b7a45fca5 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, and you cannot make a single call to retrieve history from multiple channels simultaneously. Each history request must target a specific channel.
+
+To retrieve history from multiple channels, you must iterate over your list of channels and call the history method for each one. This can be done either synchronously (one after another) or asynchronously (in parallel), depending on your performance requirements:
+
+* **Synchronous approach** - Call history for each channel sequentially, which is simpler but slower
+* **Asynchronous approach** - Make parallel history calls for better performance, then combine and sort results by timestamp if needed
+
+This pattern is commonly used in applications that implement channel sharding, where related data is distributed across multiple channels.
+
The following example retrieves the latest message sent on a channel:
From 9b15e8fac3db832ff94e11a532c94d9c3d3e0630 Mon Sep 17 00:00:00 2001
From: Francis Roberts <111994975+franrob-projects@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:24:11 +0200
Subject: [PATCH 20/22] Common misconceptions around Ably History
---
src/pages/docs/storage-history/history.mdx | 27 ++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index 4b7a45fca5..e64cc5639e 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -18,6 +18,33 @@ The history feature enables you to retrieve previously sent messages.
Two minutes of message history is available to retrieve by default. This can be extended by configuring the [storage options](/docs/storage-history/storage) for an app.
+## Understanding history's purpose
+
+The message history feature was designed to allow clients to easily catch up on published messages on a channel after being disconnected. When a connection is abruptly terminated, client libraries will automatically reconnect. If they reconnect within two minutes, the client handles resuming the connection and catching up on all messages automatically without any intervention from the application. Learn more about how [connection state recovery](/docs/connect/states#connection-state-recovery) works.
+
+However, if the connection cannot be resumed and continuity on channels is not possible, Ably provides a history API that allows clients to request messages published previously on that channel. The history feature stores messages and indexes them based on the channel they were published on, the time, and the Ably serial number. This provides an efficient way to request all history from any point in time or attachment on a channel.
+
+### History is not a database replacement
+
+A common misconception is thinking that message history can replace the role of a database for longer-term storage. While technically there is no restriction within the Ably service to store messages indefinitely, the APIs and functionality provided compared to a database typically make the history API unsuitable for complex queries.
+
+For example, if you were building a chat app, you would typically need the following features:
+
+| Requirement | History API capability |
+|-------------|----------------------|
+| Retrieve all messages since I last had the app open. | Ably's history API can do this. |
+| Find all messages published by an author. | Cannot do this - messages are only indexed by serial, time, and channel. |
+| Perform full-text search on messages. | Cannot do this, messages are only indexed by serial, time, and channel. |
+| Perform relational queries by grouping messages by thread. | Cannot do this - messages are only indexed by serial, time, and channel. |
+
+### Recommended architecture
+
+For more complex message retrieval, consider using a database designed to satisfy complex data queries. Follow this recommended design pattern:
+
+* Use Ably for real-time message distribution.
+* Consume data from Ably's pub/sub platform and persist it to a more suitable database storage solution.
+* Use the database for complex queries while maintaining Ably for real-time capabilities.
+
## 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:
From 7c74b553a1e392033c6ba726227c2ee8c353f7ef 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 21/22] How synchronized / up to date is channel history
betweendatacenters / regions?
---
src/pages/docs/storage-history/history.mdx | 29 ++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index e64cc5639e..876637612c 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -821,6 +821,35 @@ 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:
+
+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
+
+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
+
+- 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 2da0d6ab30f6b74b2b6fe4a3116882d50ad1f572 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 22/22] Tidy up commit
---
src/pages/docs/auth/capabilities.mdx | 10 ++--
src/pages/docs/channels/index.mdx | 14 +++---
src/pages/docs/messages/index.mdx | 6 +--
.../metadata-stats/metadata/subscribe.mdx | 4 +-
.../docs/platform/account/app/console.mdx | 10 ++--
.../architecture/message-ordering.mdx | 8 ++--
src/pages/docs/platform/errors/codes.mdx | 4 +-
src/pages/docs/platform/pricing/faqs.mdx | 48 +++++++++----------
.../docs/presence-occupancy/presence.mdx | 46 +++++++++---------
src/pages/docs/pub-sub/advanced.mdx | 30 ++++++------
src/pages/docs/storage-history/history.mdx | 30 ++++++------
src/pages/docs/storage-history/storage.mdx | 2 +-
12 files changed, 106 insertions(+), 106 deletions(-)
diff --git a/src/pages/docs/auth/capabilities.mdx b/src/pages/docs/auth/capabilities.mdx
index 1f90258282..ab156d0976 100644
--- a/src/pages/docs/auth/capabilities.mdx
+++ b/src/pages/docs/auth/capabilities.mdx
@@ -51,15 +51,15 @@ 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:
-* Use [token authentication](/docs/auth/token) to restrict access to channels by issuing tokens with specific capabilities only to authorized users.
-* Set specific `clientId` values in tokens to ensure only certain users can access particular channels.
-* Grant or restrict specific operations (`subscribe`, `publish`, `presence`) on channels through capability configurations.
+* [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.
+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/channels/index.mdx b/src/pages/docs/channels/index.mdx
index 3063167188..c971573091 100644
--- a/src/pages/docs/channels/index.mdx
+++ b/src/pages/docs/channels/index.mdx
@@ -46,15 +46,15 @@ var channel = ably.channels.get('foo:*');
channel.attach(); // Attempting to attach to all channels matching foo:* will fail
```
-Wildcard subscriptions are not supported for several technical and architectural reasons:
+Wildcard subscriptions are not supported for technical and architectural reasons:
-* Scalability limitations: attaching to an unbounded number of channels in a single operation does not scale for client devices or Ably's servers terminating those connections.
-* Distributed architecture: Channels in Ably's cluster are dynamically distributed across available resources and move frequently. Each channel is autonomous to ensure the system remains reliable without a single point of failure. Supporting wildcard subscriptions would require maintaining connections to every server that could possibly run a matching channel, which does not scale.
-* Data delivery guarantees: - Wildcard subscriptions make it impossible to offer the data delivery guarantees and quality of service that Ably provides because:
- * There's no deterministic way to know which channels a client is actually attached to at any point in time.
- * If a client device becomes overloaded or exceeds rate limits, servers would need to selectively drop messages across random channels, making it unclear which messages were missed.
+* Scalability limitations: attaching to an unbounded number of channels in a single operation does not scale for client devices or servers
+* Distributed architecture: Channels are dynamically distributed across available resources and move frequently. Each channel is autonomous to ensure the system remains reliable without a single point of failure. Supporting wildcard subscriptions would require maintaining connections to every server that could possibly run a matching channel
+* Data delivery guarantees: Wildcard subscriptions make it impossible to offer the data delivery guarantees and quality of service that Ably provides:
+ * No deterministic way to know which channels a client is actually attached to at any point in time
+ * If a client device becomes overloaded or exceeds rate limits, servers would need to selectively drop messages across random channels, making it unclear which messages were missed
-Because Ably connections are multiplexed, you can attach and detach from channels dynamically over the same connection. This enables you to effectively implement wildcard-like functionality by programmatically managing channel subscriptions as needed.
+Ably connections are multiplexed, so you can attach and detach from channels dynamically over the same connection. This enables you to implement wildcard-like functionality by programmatically managing channel subscriptions as needed.
Channels are identified by their unique name. The following restrictions apply to when naming a channel:
diff --git a/src/pages/docs/messages/index.mdx b/src/pages/docs/messages/index.mdx
index 53ed853fe3..349c2cef94 100644
--- a/src/pages/docs/messages/index.mdx
+++ b/src/pages/docs/messages/index.mdx
@@ -36,15 +36,15 @@ 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.
### Read receipts and acknowledgments
-Ably does not natively support read receipts or message acknowledgments to track who has read published messages. This aligns with the fundamental pub-sub pattern where publishers are decoupled from subscribers and shouldn't need to know or care about the number of recipients.
+Ably does not natively support read receipts or message acknowledgments to track who has read published messages. This aligns with the fundamental pub-sub pattern where publishers are decoupled from subscribers.
-Since Ably doesn't control how many clients are expected on a channel, it's architecturally difficult to determine who has read or received a message. However, you can implement a custom read receipt system by having subscribers publish acknowledgment messages when they consider a message received or read:
+Since Ably doesn't control how many clients are expected on a channel, it is architecturally difficult to determine who has read or received a message. You can implement a custom read receipt system by having subscribers publish acknowledgment messages when they consider a message received or read:
```javascript
// Subscriber acknowledges reading a message
diff --git a/src/pages/docs/metadata-stats/metadata/subscribe.mdx b/src/pages/docs/metadata-stats/metadata/subscribe.mdx
index f1cf9c6a25..1ea4a5b582 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
### Understanding regional channel activity
-Seeing `channel.region.inactive` events in your [Dev Console](/docs/platform/account/app/console) logs is perfectly normal and expected behavior. When channels become active, they are activated 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/errors/codes.mdx b/src/pages/docs/platform/errors/codes.mdx
index e6e9e4a603..9c62ea7863 100644
--- a/src/pages/docs/platform/errors/codes.mdx
+++ b/src/pages/docs/platform/errors/codes.mdx
@@ -684,10 +684,10 @@ This limit exists to ensure that connection state and the load imposed for conne
**Resolution:** Use one or more of the following strategies to avoid reaching this limit:
-* For SDKs that support transient publishes (ably-js >= 1.0.9, ably-ruby >= 1.1, ably-java >= 1.0.10), call `channel.publish()` without attaching to avoid the channel limit altogether. This is suitable when you only need to publish messages, not receive them.
+* For SDKs that support transient publishes (ably-js >= 1.0.9, ably-ruby >= 1.1, ably-java >= 1.0.10), call `channel.publish()` without attaching to avoid the channel limit altogether. This is suitable when only publishing messages, not receiving them.
* Call [`channel.detach()`](/docs/channels/states#detach) once finished with a channel to free up the connection slot. This is suitable when using channels for short periods of time.
* REST publishes are stateless and don't attach to channels. This is suitable when publish rates are low or transient publishes aren't supported.
-* Distribute channels across multiple realtime connections. This is suitable when you genuinely need hundreds of simultaneous channel attachments.
+* Distribute channels across multiple realtime connections. This is suitable when hundreds of simultaneous channel attachments are needed.
* Contact Ably to configure higher limits for applications requiring very high channel counts per connection.
Remember that subscribing to a channel (and publishing in SDKs without transient publishing support) will implicitly attach to it, so detaching after these operations helps keep your channel count manageable.
diff --git a/src/pages/docs/platform/pricing/faqs.mdx b/src/pages/docs/platform/pricing/faqs.mdx
index 6138c01ed4..41aa53cdaa 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.
@@ -49,7 +49,7 @@ The maximum message size is based on the [package type](/docs/pricing#packages).
If the `data` is binary and the message is sent on a text transport with base64 encoded data, and has an encoding attribute of base64, the size is calculated using the actual size of the binary data, not its base64 encoded string.
-If [publishing](/docs/api/realtime-sdk/channels#publish) an array of messages, the message size limit applies to the sum of all messages in the array, as they are treated as a single unit by the Ably platform. This is even though the are published and broadcast atomically.
+If [publishing](/docs/api/realtime-sdk/channels#publish) an array of messages, the message size limit applies to the sum of all messages in the array, as they are treated as a single unit by the Ably platform, even though they are published and broadcast atomically.
### How does Ably count and bill for messages?
@@ -103,11 +103,11 @@ Messages over 2KiB count as multiple messages for billing. For example, a 16KiB
### How can I reduce my message count?
-First, understand how messages are counted by reading the [message counting section](#message-counting) above. Then use these strategies to optimize your usage:
+Understand how messages are counted by reading the [message counting section](#message-counting) above, then use these strategies to optimize usage:
#### Analyze your usage with statistics API
-Use the [Statistics API](/docs/metadata-stats/stats) to get a detailed breakdown of message usage by type (REST/realtime publishes, realtime receives, history, presence, integrations) by minute, hour, day, or month. This will identify which areas consume the most messages.
+Use the [Statistics API](/docs/metadata-stats/stats) to get a detailed breakdown of message usage by type (REST/realtime publishes, realtime receives, history, presence, integrations) by minute, hour, day, or month. This identifies which areas consume the most messages.
#### Optimize realtime messages
@@ -148,12 +148,12 @@ Presence message usage scales approximately as the square of attached clients, s
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 is not any buffer on them, or because you have exceeded the buffer. Exceeding the limit depends on the limit:
+Limits come into force either because there is no buffer on them, or because the buffer has been exceeded. The effect depends on the limit type:
-- **Count-based limits**: Only a set amount of resources can be in use simultaneously. This means that any usage in excess of that limit will be restricted until existing resources are removed, such as the number of concurrent connections, or concurrent channels.
-- **Rate-based limits**: The rate at which resources can be used, expressed as their frequency per second. Usage in excess of a rate limit can be rejected locally, such as a publisher trying to publish at a rate in excess of the publish rate per channel. The publisher will have any publish attempts in excess of this rate rejected and have an error code returned. Other rate limits apply rate suppression across your account when exceeded. For example, if the limit for publishing messages into a queue is exceeded by twice the rate, then each message will have a 50% chance of being rejected. The suppression probability is continuously updated based on the publishing rate.
+- Count-based limits: Only a set amount of resources can be in use simultaneously. Usage in excess of that limit will be restricted until existing resources are removed, such as the number of concurrent connections, or concurrent channels.
+- Rate-based limits: The rate at which resources can be used, expressed as their frequency per second. Usage in excess of a rate limit can be rejected locally, such as a publisher trying to publish at a rate in excess of the publish rate per channel. The publisher will have any publish attempts in excess of this rate rejected and have an error code returned. Other rate limits apply rate suppression across accounts when exceeded. For example, if the limit for publishing messages into a queue is exceeded by twice the rate, then each message will have a 50% chance of being rejected. The suppression probability is continuously updated based on the publishing rate.
## Data and compliance
@@ -169,15 +169,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.
@@ -197,13 +197,13 @@ No. A business associate is “a person or organization, other than a member of
Ably could be a business associate under this definition, however, the security rule further defines business associates as “organizations are not considered business associates if their functions or services do not involve the use or disclosure of protected health information, and **where any access to protected health information by such persons would be incidental, if at all**.”
-Whilst Ably may be used by covered entities to transport individually identifiable health information, Ably does not inspect the data it transports. Ably never inspects payloads. Ably treats them as opaque. Ably is a conduit for data (a 'dumb pipe') like the postal service in the physical world.
+While Ably may be used by covered entities to transport individually identifiable health information, Ably does not inspect the data it transports. Ably never inspects payloads. Ably treats them as opaque. Ably is a conduit for data (a 'dumb pipe') like the postal service in the physical world.
#### Does Ably transport individually identifiable health information?
-As a transport for information Ably does not know the nature of the data being handled. It is possible for Ably 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.
+As a transport for information, Ably does not know the nature of the data being handled. Ably customers, who may be covered entities under HIPAA, can transport individually identifiable health information. 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?
@@ -213,7 +213,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.
@@ -225,7 +225,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 Ably has such safeguards in place as a matter of course and most business associate agreements are standard. Ably also recognizes 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.
@@ -237,17 +237,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, while 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 c5ab6997ec..78704d18b4 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,19 +617,19 @@ 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).
+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).
## Why presence member limits exist
-It's important to understand that there are no limits on the number of clients that can [attach](/docs/channels/states#attach) and [subscribe](/docs/pub-sub) to a channel for regular messaging. Presence limits apply specifically to the presence feature only.
+There are no limits on the number of clients that can [attach](/docs/channels/states#attach) and [subscribe](/docs/pub-sub) to a channel for regular messaging. Presence limits apply specifically to the presence feature only.
-The limits on presence members are largely not driven by constraints within the Ably platform, but instead exist as a precaution for customers who want very large presence sets and may not realize the implications of this approach.
+The limits on presence members are largely not driven by constraints within the Ably platform, but instead exist as a precaution for customers who want very large presence sets and may not realize the implications.
### Presence scaling implications
-To understand why limits exist, consider what happens when members enter or leave presence on a channel:
+Consider what happens when members enter or leave presence on a channel:
* Only one enter event is emitted back to the user (2 total messages: 1 publish, 1 subscribe)
* Two users receive the event (3 total messages: 1 publish, 2 subscribe)
@@ -641,7 +641,7 @@ Following this pattern, if 1,000 users enter a channel:
* If all users then left: 1,000 leave events published + 501,000 leave events received.
* Total: Over 1 million events for 1,000 members entering and leaving.
-If all 1,000 users left over 10 seconds, messages would be published at a rate of **50,000 per second**, which could impact performance and incur significant costs.
+If all 1,000 users left over 10 seconds, messages would be published at a rate of 50,000 per second, which could impact performance and incur significant costs.
### Managing large presence sets
@@ -655,7 +655,7 @@ When considering channels with many present members, evaluate these factors:
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:
@@ -696,11 +696,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.
@@ -721,13 +721,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.
@@ -931,11 +931,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:
@@ -1297,7 +1297,7 @@ Note that the 15 second delay from being removed from the presence set is only f
### 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.
@@ -1306,7 +1306,7 @@ When using the JavaScript client library (ably-js), you may notice that closing
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 3bd17068c4..1f84f381ee 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,20 +949,20 @@ 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.
#### Restrictions for atomic publishing
When publishing multiple messages atomically (e.g., by calling `publish()` with an array of messages), those messages are bundled together and will either all succeed or all fail. For idempotency purposes, there is only a single idempotency key for the entire array of messages.
-To prevent confusion about individual message idempotency, Ably requires that all messages in an atomic publish must have IDs 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.
+Ably requires that all messages in an atomic publish must have IDs derived from a single base ID to prevent confusion about individual message idempotency. 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 atomically with a base ID of `foo`, the messages must have IDs `foo:0`, `foo:1`, and `foo:2`. This emphasizes that the messages share a single idempotency key (`foo`).
+For example, to publish 3 messages atomically with a base ID of `foo`, the messages must have IDs `foo:0`, `foo:1`, and `foo:2`. The messages share a single idempotency key (`foo`).
```javascript
// Correct: atomic publishing with derived IDs
@@ -983,7 +983,7 @@ If you want messages to be individually idempotent, publish them separately usin
## Troubleshooting message delivery
-If you're not receiving messages, follow these systematic debugging steps to identify the issue:
+If you're not receiving messages, follow these systematic debugging steps:
### Common causes
diff --git a/src/pages/docs/storage-history/history.mdx b/src/pages/docs/storage-history/history.mdx
index 876637612c..c9da05c8c0 100644
--- a/src/pages/docs/storage-history/history.mdx
+++ b/src/pages/docs/storage-history/history.mdx
@@ -22,11 +22,11 @@ Two minutes of message history is available to retrieve by default. This can be
The message history feature was designed to allow clients to easily catch up on published messages on a channel after being disconnected. When a connection is abruptly terminated, client libraries will automatically reconnect. If they reconnect within two minutes, the client handles resuming the connection and catching up on all messages automatically without any intervention from the application. Learn more about how [connection state recovery](/docs/connect/states#connection-state-recovery) works.
-However, if the connection cannot be resumed and continuity on channels is not possible, Ably provides a history API that allows clients to request messages published previously on that channel. The history feature stores messages and indexes them based on the channel they were published on, the time, and the Ably serial number. This provides an efficient way to request all history from any point in time or attachment on a channel.
+If the connection cannot be resumed and continuity on channels is not possible, Ably provides a history API that allows clients to request messages published previously on that channel. The history feature stores messages and indexes them based on the channel they were published on, the time, and the Ably serial number. This provides an efficient way to request all history from any point in time or attachment on a channel.
### History is not a database replacement
-A common misconception is thinking that message history can replace the role of a database for longer-term storage. While technically there is no restriction within the Ably service to store messages indefinitely, the APIs and functionality provided compared to a database typically make the history API unsuitable for complex queries.
+Message history cannot replace a database for longer-term storage. While technically there is no restriction within the Ably service to store messages indefinitely, the APIs and functionality provided compared to a database typically make the history API unsuitable for complex queries.
For example, if you were building a chat app, you would typically need the following features:
@@ -47,7 +47,7 @@ For more complex message retrieval, consider using a database designed to satisf
## 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.
@@ -65,12 +65,12 @@ History is stored per channel, and you cannot make a single call to retrieve his
To retrieve history from multiple channels, you must iterate over your list of channels and call the history method for each one. This can be done either synchronously (one after another) or asynchronously (in parallel), depending on your performance requirements:
-* **Synchronous approach** - Call history for each channel sequentially, which is simpler but slower
-* **Asynchronous approach** - Make parallel history calls for better performance, then combine and sort results by timestamp if needed
+* Synchronous approach - Call history for each channel sequentially, which is simpler but slower
+* Asynchronous approach - Make parallel history calls for better performance, then combine and sort results by timestamp if needed
This pattern is commonly used in applications that implement 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
@@ -329,7 +329,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 |
|-----------|-------------|
@@ -353,7 +353,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
@@ -447,13 +447,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
@@ -598,7 +598,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
@@ -812,7 +812,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 |
|-----------|-------------|
@@ -835,7 +835,7 @@ When a message is published:
### Storage consistency
-The following table explains storage consistency levels:
+Storage consistency levels:
| Storage type | Synchronization time | Consistency level |
|-------------|---------------------|------------------|
@@ -854,7 +854,7 @@ The following table explains storage consistency levels:
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: