diff --git a/docusaurus/docs/React/components/contexts/channel-state-context.mdx b/docusaurus/docs/React/components/contexts/channel-state-context.mdx
index 1aca9856e6..354d315ac2 100644
--- a/docusaurus/docs/React/components/contexts/channel-state-context.mdx
+++ b/docusaurus/docs/React/components/contexts/channel-state-context.mdx
@@ -104,7 +104,7 @@ A custom function to provide size configuration for image attachments
 
 | Type                                                             |
 | ---------------------------------------------------------------- |
-| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` |
+| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfiguration` |
 
 ### hasMore
 
diff --git a/docusaurus/docs/React/components/contexts/component-context.mdx b/docusaurus/docs/React/components/contexts/component-context.mdx
index 963a486254..48963fcd51 100644
--- a/docusaurus/docs/React/components/contexts/component-context.mdx
+++ b/docusaurus/docs/React/components/contexts/component-context.mdx
@@ -35,7 +35,7 @@ Custom UI component to display a attachment previews in `MessageInput`.
 
 | Type      | Default                                                                             |
 | --------- | ----------------------------------------------------------------------------------- |
-| component | <GHComponentLink text='Attachment' path='/MessageInput/AttachmentPreviewList.tsx'/> |
+| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |
 
 ### AutocompleteSuggestionHeader
 
diff --git a/docusaurus/docs/React/components/core-components/channel.mdx b/docusaurus/docs/React/components/core-components/channel.mdx
index 06a6f26057..efb3f655c7 100644
--- a/docusaurus/docs/React/components/core-components/channel.mdx
+++ b/docusaurus/docs/React/components/core-components/channel.mdx
@@ -122,7 +122,7 @@ A list of accepted file upload types.
 
 ### activeUnreadHandler
 
-Custom handler function that runs when the active channel has unread messages (i.e., when chat is running on a separate browser tab).
+Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab.
 
 | Type                                            |
 | ----------------------------------------------- |
@@ -138,11 +138,11 @@ Custom UI component to display a message attachment.
 
 ### AttachmentPreviewList
 
-Custom UI component to display a attachment previews in `MessageInput`.
+Custom UI component to display an attachment previews in `MessageInput`.
 
 | Type      | Default                                                                             |
 | --------- | ----------------------------------------------------------------------------------- |
-| component | <GHComponentLink text='Attachment' path='/MessageInput/AttachmentPreviewList.tsx'/> |
+| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |
 
 ### AutocompleteSuggestionHeader
 
@@ -422,7 +422,7 @@ A custom function to provide size configuration for image attachments
 
 | Type                                                             |
 | ---------------------------------------------------------------- |
-| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` |
+| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfiguration` |
 
 ### initializeOnMount
 
@@ -481,7 +481,7 @@ Configuration parameter to mark the active channel as read when mounted (opened)
 
 | Type    | Default |
 | ------- | ------- |
-| boolean | false   |
+| boolean | true   |
 
 ### Input
 
@@ -497,7 +497,7 @@ Custom component to render link previews in `MessageInput`.
 
 | Type      | Default                                                                             |
 | --------- | ----------------------------------------------------------------------------------- |
-| component | <GHComponentLink text='MessageInputFlat' path='/MessageInput/LinkPreviewList.tsx'/> |
+| component | <GHComponentLink text='LinkPreviewList' path='/MessageInput/LinkPreviewList.tsx'/> |
 
 ### LoadingErrorIndicator
 
diff --git a/docusaurus/docs/React/components/core-components/message-list.mdx b/docusaurus/docs/React/components/core-components/message-list.mdx
index 88232733fc..785a2f4577 100644
--- a/docusaurus/docs/React/components/core-components/message-list.mdx
+++ b/docusaurus/docs/React/components/core-components/message-list.mdx
@@ -459,14 +459,6 @@ Function called when more messages are to be loaded, provide your own function t
 | -------- | ---------------------------------------------------------------------------------------- |
 | function | [ChannelActionContextValue['loadMore']](../contexts/channel-action-context.mdx#loadmore) |
 
-### markReadOnScrolledToBottom
-
-When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list.
-
-| Type    | Default |
-|---------|---------|
-| boolean | false   |
-
 ### Message
 
 Custom UI component to display an individual message.
@@ -623,6 +615,15 @@ channel, the `MessageNotification` component displays when new messages arrive.
 | ------ | ------- |
 | number | 200     |
 
+### showUnreadNotificationAlways
+
+The floating notification informing about unread messages will be shown when the `UnreadMessagesSeparator` is not visible. The default is false, that means the notification
+is shown only when viewing unread messages.
+
+| Type    | Default |
+|---------|---------|
+| boolean | false   |
+
 ### threadList
 
 If true, indicates that the current `MessageList` component is part of a `Thread`.
diff --git a/docusaurus/docs/React/components/core-components/virtualized-list.mdx b/docusaurus/docs/React/components/core-components/virtualized-list.mdx
index d662ada496..47cc72dca6 100644
--- a/docusaurus/docs/React/components/core-components/virtualized-list.mdx
+++ b/docusaurus/docs/React/components/core-components/virtualized-list.mdx
@@ -174,14 +174,6 @@ Function called when more messages are to be loaded, provide your own function t
 | -------- | ---------------------------------------------------------------------------------------- |
 | function | [ChannelActionContextValue['loadMore']](../contexts/channel-action-context.mdx#loadmore) |
 
-### markReadOnScrolledToBottom
-
-When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list.
-
-| Type    | Default |
-|---------|---------|
-| boolean | false   |
-
 ### Message
 
 Custom UI component to display an individual message.
@@ -254,6 +246,15 @@ If true, the Giphy preview will render as a separate component above the `Messag
 | ------- | ------- |
 | boolean | false   |
 
+### showUnreadNotificationAlways
+
+The floating notification informing about unread messages will be shown when the `UnreadMessagesSeparator` is not visible. The default is false, that means the notification
+is shown only when viewing unread messages.
+
+| Type    | Default |
+|---------|---------|
+| boolean | false   |
+
 ### stickToBottomScrollBehavior
 
 The scroll-to behavior when new messages appear. Use `'smooth'` for regular chat channels and `'auto'`
diff --git a/docusaurus/docs/React/guides/channel-read-state.mdx b/docusaurus/docs/React/guides/channel-read-state.mdx
index 533c370e2e..7c1ba5bb99 100644
--- a/docusaurus/docs/React/guides/channel-read-state.mdx
+++ b/docusaurus/docs/React/guides/channel-read-state.mdx
@@ -9,7 +9,9 @@ This guide intends to provide an overview how channel read state is handled by d
 
 ## The model
 
-The React SDK maintains channel read state for UI components inside `Channel` component in a separate variable `channelUnreadUiState`. Channel read state reflecting the back-end state, can be accessed via `channel.state.read` mapping.
+The React SDK maintains channel read state for UI components inside `Channel` component in a separate variable `channelUnreadUiState`. This state is dedicated to show unread count on components `UnreadMessagesSeparator` and `UnreadMessagesNotification` (or other custom components that need its behavior). The `channelUnreadUiState` is special in that when a channel is opened and marked read, the `channelUnreadUiState` does not reflect this initial update. This is in order the user can see, how many unread messages there have been left since the previous session.
+
+Channel read state reflecting the current back-end state can be accessed via `channel.state.read` mapping.
 
 ### Channel UI unread state
 
@@ -25,7 +27,7 @@ The state is maintained by `Channel` component and shared with its children via
 
 ### Channel read state for all users
 
-The read state is extracted from the channel query response, specifically from each `ChannelResponse` object's `read` attribute This is internally transformed from an array of users' read statuses into and object indexed by user id. The read state is updated upon receiving WS events like `message.read`, `notification.mark_unread`. Each value of the `read` state object has then the following structure:
+The read state is extracted from the channel query response, specifically from each `ChannelResponse` object's `read` attribute. This is internally transformed from an array of users' read statuses into and object indexed by user id. The read state is updated upon receiving WS events like `message.read`, `notification.mark_unread`, `message.new`. Each value of the `read` state object has then the following structure:
 
 | Property                    | Type                    | Description                                                                                                                                                                                                                                  |
 |-----------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -35,6 +37,10 @@ The read state is extracted from the channel query response, specifically from e
 | **first_unread_message_id** | `string` or `undefined` | The ID of the message that was marked unread (`notification.mark_unread` event). The value is available only when a message is marked unread. Therefore, cannot be relied on to place unread messages UI.                                    |
 | **last_read_message_id**    | `string` or `undefined` | The ID of the message preceding the first unread message. The value is provided with `ChannelResponse` when querying channels or on `notification.mark_unread` event.                                                                        |
 
+:::note
+Be aware that only the last 100 newest messages can be marked unread. If older messages are marked unread, an error notification is shown informing about this limitation.
+:::
+
 ### Access the read state
 
 In the SDK, the `read` and `channelUnreadUiState` can be accessed via [`useChannelStateContext` consumer](../../components/contexts/channel_state_context#read):
@@ -78,7 +84,6 @@ The function accepts a single `options` parameter of the following format:
 | Field                  | Type      | Optional | Description                                                                                                                                                                                                                                                                                                                 |
 |------------------------|-----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | `updateChannelUiUnreadState` | `boolean` | yes      | Signal, whether the `channelUnreadUiState` should be updated. The local state update is prevented when the Channel component is mounted. This is in order to keep the UI indicating the original unread state, when the user opens a channel. If the value for `updateChannelUiUnreadState` is not provided, the state is updated. |
-|
 
 :::important
 Please, prefer using the `markRead()` function everywhere in the `Channel` context as this function throttles the API calls thus preventing you from hitting the API limit of mark-read calls.
@@ -90,8 +95,8 @@ The default components included in **marking a channel read**:
 
 | Component                                                                                                                                     | Description                                                                                                                                                                                    |
 |-----------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [`Channel`](../../components/core-components/channel)                                                                                         | Can be configured to mark active channel read when mounted. This can be done through its prop `markReadOnMount`. By default disabled.                                                          |
-| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | Can be configured to mark channel read when message list is scrolled to the bottom. This can be done through the list's prop `markReadOnScrolledToBottom`. By default disabled.                |
+| [`Channel`](../../components/core-components/channel)                                                                                         | Can be configured to mark active channel read when mounted. This can be done through its prop `markReadOnMount`. By default enabled.                                                          |
+| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | Marks channel read when message list is scrolled to the bottom.               |
 | [`UnreadMessagesNotification`](../../components/contexts/component_context#unreadmessagesnotification)                                        | Floating notification rendered in the message list. Contains a button, which when clicked, marks the channel read.                                                                             |
 
 The default components included in **marking a channel unread**:
@@ -118,13 +123,16 @@ Message threads do not participate in handling read state of a channel. Thread r
 The channel is marked read in the following scenarios:
 
 1. User enters a channel with unread messages if `Channel` prop `markReadOnMount` is enabled (default behavior).
-2. User clicks the button on the default `UnreadMessagesNotification` component to mark channel read.
+2. User scrolls up and back down to the latest message.
+3. User clicks the button on the default `UnreadMessagesNotification` component to mark channel read.
 
 The channel is marked unread in the following scenarios:
 
 1. User with `read-events` permission selects `Mark as unread` option from the `MessageActionsBox`.
 
-The component `UnreadMessagesSeparator` is shown immediately below the last read message. It can be followed by own message or a message posted by another user. It does not show unread count if among the unread messages are only own messages (own message can be marked unread).
+The component `UnreadMessagesSeparator` is shown immediately below the last read message. It can be followed by own message or a message posted by another user. It does not show unread count if:
+- `showCount` prop is enabled and among the unread messages are only own messages (own message can be marked unread).
+- `showCount` prop is disabled (default)
 
 ## Channel read state handling customization
 
@@ -132,12 +140,9 @@ The component `UnreadMessagesSeparator` is shown immediately below the last read
 
 There is a possibility to configure when a channel is marked read by tweaking these default components' props:
 
-| Component                                                                                                                                     | Prop                                                |
-|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|
-| [`Channel`](../../components/core-components/channel)                                                                                         | `markReadOnMount` (by default disabled)             |
-| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | `markReadOnScrolledToBottom` (by default disabled)  |
-
-And so instead of marking a channel read on `Channel` component mount, the channel can be marked read when scrolled to the bottom.
+| Component                                             | Prop                                   |
+|-------------------------------------------------------|----------------------------------------|
+| [`Channel`](../../components/core-components/channel) | `markReadOnMount` (by default enabled) |
 
 ### Customization through custom components
 
@@ -163,9 +168,26 @@ const Component = ({children}) => (
 );
 ```
 
+The component can be configured through the following props:
+
+| Prop                | Description                                                                                           | Type      | Default |
+|---------------------|-------------------------------------------------------------------------------------------------------|-----------|---------|
+| `showCount`         | Configuration parameter to determine, whether the unread count is to be shown on the component.       | `boolean` | `false` |
+
+```tsx
+import {
+    UnreadMessagesSeparator as StreamUnreadMessagesSeparator,
+    UnreadMessagesSeparatorProps,
+} from 'stream-chat-react';
+
+const UnreadMessagesSeparator = (props: UnreadMessagesSeparatorProps) => {
+    return <StreamUnreadMessagesSeparator {...props} showCount />
+}
+```
+
 #### Custom `UnreadMessagesNotification` component
 
-Will be rendered only when being scrolled to unread messages in a message list. The default implementation positions the notification as a floating element above the messages in a message list.
+Will be rendered only when `UnreadMessagesSeparator` is not visible in message list. The default implementation positions the notification as a floating element above the messages in a message list. It shows the number of unread messages since the user has scrolled away from the latest message (bottom of the message list).
 
 ```tsx
 import {
@@ -183,9 +205,30 @@ const Component = ({children}) => (
 );
 ```
 
+The component can be configured through the following props:
+
+| Prop                | Description                                                                                           | Type      | Default |
+|---------------------|-------------------------------------------------------------------------------------------------------|-----------|---------|
+| `showCount`         | Configuration parameter to determine, whether the unread count is to be shown on the component.       | `boolean` | `false` |
+| `queryMessageLimit` | Configuration parameter to determine the message page size, when jumping to the first unread message. | `number`  | `100`   |
+
+
+```tsx
+import {
+    UnreadMessagesNotification as StreamUnreadMessagesNotification,
+    UnreadMessagesNotificationProps,
+} from 'stream-chat-react';
+
+const UnreadMessagesNotification = (props: UnreadMessagesNotificationProps) => {
+    return <StreamUnreadMessagesNotification {...props} queryMessageLimit={50} showCount />
+}
+```
+
 #### Custom `MessageNotification` component
 
-The SDK exports [`ScrollToBottomButton`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/ScrollToBottomButton.tsx) that shows the unread count and is rendered only when scrolled away from the bottom of the message list. We can however implement our own message notification component.
+The SDK exports [`ScrollToBottomButton`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/ScrollToBottomButton.tsx) that shows the unread count since the point the user has scrolled away from the newest messages in the list.
+
+We can implement our own message notification component.
 
 ```tsx
 import {
@@ -208,7 +251,7 @@ const Component = ({children}) => (
 
 ### Default SDK component to jump to the first unread message
 
-The SDK provides a component `UnreadMessagesNotification`, that when clicked on the part `X Unread messages`, the message list scrolls to the first unread message. If the first unread message is not loaded in the local channel state, the message is retrieved from the API.
+The SDK provides a component `UnreadMessagesNotification`, that when clicked on the part `Unread messages`, the message list scrolls to the first unread message. If the first unread message is not loaded in the local channel state, the message is retrieved from the API.
 
 
 ### API to jump to the first unread message
diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx
index dd64fab106..b7c5601640 100644
--- a/src/components/Channel/Channel.tsx
+++ b/src/components/Channel/Channel.tsx
@@ -207,7 +207,7 @@ export type ChannelProps<
 > = ChannelPropsForwardedToComponentContext<StreamChatGenerics> & {
   /** List of accepted file types */
   acceptedFiles?: string[];
-  /** Custom handler function that runs when the active channel has unread messages (i.e., when chat is running in a separate browser tab) */
+  /** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
   activeUnreadHandler?: (unread: number, documentTitle: string) => void;
   /** The connected and active channel */
   channel?: StreamChannel<StreamChatGenerics>;
@@ -384,7 +384,7 @@ const ChannelInner = <
   const [channelConfig, setChannelConfig] = useState(channel.getConfig());
   const [notifications, setNotifications] = useState<ChannelNotifications>([]);
   const [quotedMessage, setQuotedMessage] = useState<StreamMessage<StreamChatGenerics>>();
-  const [channelUnreadUiState, setChannelUnreadUiState] = useState<ChannelUnreadUiState>();
+  const [channelUnreadUiState, _setChannelUnreadUiState] = useState<ChannelUnreadUiState>();
 
   const notificationTimeouts: Array<NodeJS.Timeout> = [];
 
@@ -398,7 +398,7 @@ const ChannelInner = <
   const isMounted = useIsMounted();
 
   const originalTitle = useRef('');
-  const lastRead = useRef(new Date());
+  const lastRead = useRef<Date | undefined>();
   const online = useRef(true);
 
   const channelCapabilitiesArray = channel.data?.own_capabilities as string[];
@@ -412,47 +412,56 @@ const ChannelInner = <
     },
   );
 
-  // eslint-disable-next-line react-hooks/exhaustive-deps
-  const markRead = useCallback(
-    throttle(
-      async (options?: MarkReadWrapperOptions) => {
-        const { updateChannelUiUnreadState = true } = options ?? {};
-        if (channel.disconnected || !channelConfig?.read_events) {
-          return;
-        }
+  const setChannelUnreadUiState = useMemo(
+    () =>
+      throttle(_setChannelUnreadUiState, 200, {
+        leading: true,
+        trailing: false,
+      }),
+    [],
+  );
 
-        lastRead.current = new Date();
+  const markRead = useMemo(
+    () =>
+      throttle(
+        async (options?: MarkReadWrapperOptions) => {
+          const { updateChannelUiUnreadState = true } = options ?? {};
+          if (channel.disconnected || !channelConfig?.read_events) {
+            return;
+          }
 
-        try {
-          if (doMarkReadRequest) {
-            doMarkReadRequest(
-              channel,
-              updateChannelUiUnreadState ? setChannelUnreadUiState : undefined,
-            );
-          } else {
-            const markReadResponse = await channel.markRead();
-            if (updateChannelUiUnreadState && markReadResponse) {
-              setChannelUnreadUiState({
-                last_read: lastRead.current,
-                last_read_message_id: markReadResponse.event.last_read_message_id,
-                unread_messages: 0,
-              });
+          lastRead.current = new Date();
+
+          try {
+            if (doMarkReadRequest) {
+              doMarkReadRequest(
+                channel,
+                updateChannelUiUnreadState ? setChannelUnreadUiState : undefined,
+              );
+            } else {
+              const markReadResponse = await channel.markRead();
+              if (updateChannelUiUnreadState && markReadResponse) {
+                _setChannelUnreadUiState({
+                  last_read: lastRead.current,
+                  last_read_message_id: markReadResponse.event.last_read_message_id,
+                  unread_messages: 0,
+                });
+              }
             }
-          }
 
-          if (activeUnreadHandler) {
-            activeUnreadHandler(0, originalTitle.current);
-          } else if (originalTitle.current) {
-            document.title = originalTitle.current;
+            if (activeUnreadHandler) {
+              activeUnreadHandler(0, originalTitle.current);
+            } else if (originalTitle.current) {
+              document.title = originalTitle.current;
+            }
+          } catch (e) {
+            console.error(t<string>('Failed to mark channel as read'));
           }
-        } catch (e) {
-          console.error(t<string>('Failed to mark channel as read'));
-        }
-      },
-      500,
-      { leading: true, trailing: false },
-    ),
-    [activeUnreadHandler, channel, channelConfig, doMarkReadRequest, t],
+        },
+        500,
+        { leading: true, trailing: false },
+      ),
+    [activeUnreadHandler, channel, channelConfig, doMarkReadRequest, setChannelUnreadUiState, t],
   );
 
   const handleEvent = async (event: Event<StreamChatGenerics>) => {
@@ -475,11 +484,7 @@ const ChannelInner = <
     }
 
     if (event.type === 'message.new') {
-      let mainChannelUpdated = true;
-
-      if (event.message?.parent_id && !event.message?.show_in_channel) {
-        mainChannelUpdated = false;
-      }
+      const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
 
       if (mainChannelUpdated) {
         if (document.hidden && channelConfig?.read_events && !channel.muteStatus().muted) {
@@ -524,7 +529,7 @@ const ChannelInner = <
     }
 
     if (event.type === 'notification.mark_unread')
-      setChannelUnreadUiState((prev) => {
+      _setChannelUnreadUiState((prev) => {
         if (!(event.last_read_at && event.user)) return prev;
         return {
           first_unread_message_id: event.first_unread_message_id,
@@ -588,7 +593,7 @@ const ChannelInner = <
         if (client.user?.id && channel.state.read[client.user.id]) {
           // eslint-disable-next-line @typescript-eslint/no-unused-vars
           const { user, ...ownReadState } = channel.state.read[client.user.id];
-          setChannelUnreadUiState(ownReadState);
+          _setChannelUnreadUiState(ownReadState);
         }
         /**
          * TODO: maybe pass last_read to the countUnread method to get proper value
@@ -760,7 +765,7 @@ const ChannelInner = <
 
   const jumpToFirstUnreadMessage = useCallback(
     async (queryMessageLimit = 100) => {
-      if (!client.user) return;
+      if (!(client.user && channelUnreadUiState?.unread_messages)) return;
       if (!channelUnreadUiState?.last_read_message_id) {
         addNotification(t('Failed to jump to the first unread message'), 'error');
         return;
@@ -1107,6 +1112,7 @@ const ChannelInner = <
       removeMessage,
       retrySendMessage,
       sendMessage,
+      setChannelUnreadUiState,
       setQuotedMessage,
       skipMessageDataMemoization,
       updateMessage,
@@ -1124,6 +1130,7 @@ const ChannelInner = <
       jumpToFirstUnreadMessage,
       jumpToMessage,
       jumpToLatestMessage,
+      setChannelUnreadUiState,
     ],
   );
 
diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js
index b6d0f5c22f..d7fdf6bd3d 100644
--- a/src/components/Channel/__tests__/Channel.test.js
+++ b/src/components/Channel/__tests__/Channel.test.js
@@ -788,6 +788,7 @@ describe('Channel', () => {
           expect(contextMessageCount).toBe(firstPageOfMessages.length);
         });
       });
+
       it('should load the second page, if the previous query has returned message count equal default messages limit', async () => {
         const { channel, chatClient } = await initClient();
         const firstPageMessages = Array.from({ length: 25 }, generateMessage);
@@ -916,7 +917,7 @@ describe('Channel', () => {
 
       afterEach(jest.resetAllMocks);
 
-      it('should not query messages around the last read message if the last read message is unknown', async () => {
+      it('should not query messages around the last read message if the unread count is falsy', async () => {
         const {
           channels: [channel],
           client: chatClient,
@@ -925,6 +926,38 @@ describe('Channel', () => {
             {
               messages: [generateMessage()],
               read: [{ last_read: new Date().toISOString(), user }],
+              unread_messages: 0,
+            },
+          ],
+          customUser: user,
+        });
+        const loadMessageIntoState = jest
+          .spyOn(channel.state, 'loadMessageIntoState')
+          .mockImplementation();
+
+        let hasJumped;
+        await renderComponent({ channel, chatClient }, ({ jumpToFirstUnreadMessage }) => {
+          if (hasJumped) {
+            return;
+          }
+          jumpToFirstUnreadMessage();
+          hasJumped = true;
+        });
+
+        await waitFor(() => {
+          expect(loadMessageIntoState).not.toHaveBeenCalled();
+        });
+      });
+
+      it('should not query messages around the last read message if the last read message is unknown', async () => {
+        const {
+          channels: [channel],
+          client: chatClient,
+        } = await initClientWithChannels({
+          channelsData: [
+            {
+              messages: [generateMessage()],
+              read: [{ last_read: new Date().toISOString(), unread_messages: 1, user }],
             },
           ],
           customUser: user,
@@ -937,8 +970,12 @@ describe('Channel', () => {
         let notifications;
         await renderComponent(
           { channel, chatClient },
-          ({ jumpToFirstUnreadMessage, notifications: contextNotifications }) => {
-            if (hasJumped) {
+          ({
+            channelUnreadUiState,
+            jumpToFirstUnreadMessage,
+            notifications: contextNotifications,
+          }) => {
+            if (hasJumped || !channelUnreadUiState) {
               notifications = contextNotifications;
               return;
             }
@@ -954,6 +991,50 @@ describe('Channel', () => {
         });
       });
 
+      it('should not query messages around the last read message if the last read message is unknown and show error notification', async () => {
+        const {
+          channels: [channel],
+          client: chatClient,
+        } = await initClientWithChannels({
+          channelsData: [
+            {
+              messages: [generateMessage()],
+              read: [{ last_read: new Date().toISOString(), unread_messages: 1, user }],
+            },
+          ],
+          customUser: user,
+        });
+        const loadMessageIntoState = jest
+          .spyOn(channel.state, 'loadMessageIntoState')
+          .mockImplementation();
+
+        let hasJumped;
+        let notifications;
+        await act(async () => {
+          await renderComponent(
+            { channel, chatClient },
+            ({
+              channelUnreadUiState,
+              jumpToFirstUnreadMessage,
+              notifications: contextNotifications,
+            }) => {
+              if (hasJumped || !channelUnreadUiState) {
+                notifications = contextNotifications;
+                return;
+              }
+              jumpToFirstUnreadMessage();
+              hasJumped = true;
+            },
+          );
+        });
+
+        await waitFor(() => {
+          expect(loadMessageIntoState).not.toHaveBeenCalled();
+          expect(notifications).toHaveLength(1);
+          expect(notifications[0].text).toBe(errorNotificationText);
+        });
+      });
+
       it('should not query messages around last read message if the message is already loaded in state', async () => {
         const lastReadMessage = generateMessage({ id: last_read_message_id });
         const {
@@ -964,7 +1045,12 @@ describe('Channel', () => {
             {
               messages: [lastReadMessage, generateMessage()],
               read: [
-                { last_read: lastReadMessage.created_at.toISOString(), last_read_message_id, user },
+                {
+                  last_read: lastReadMessage.created_at.toISOString(),
+                  last_read_message_id,
+                  unread_messages: 1,
+                  user,
+                },
               ],
             },
           ],
@@ -1006,7 +1092,14 @@ describe('Channel', () => {
           channelsData: [
             {
               messages: [generateMessage()],
-              read: [{ last_read: new Date().toISOString(), last_read_message_id, user }],
+              read: [
+                {
+                  last_read: new Date().toISOString(),
+                  last_read_message_id,
+                  unread_messages: 1,
+                  user,
+                },
+              ],
             },
           ],
           customUser: user,
@@ -1015,14 +1108,17 @@ describe('Channel', () => {
           .spyOn(channel.state, 'loadMessageIntoState')
           .mockImplementation();
         let hasJumped;
-        await renderComponent(
-          { channel, chatClient },
-          ({ channelUnreadUiState, jumpToFirstUnreadMessage }) => {
-            if (hasJumped || !channelUnreadUiState) return;
-            jumpToFirstUnreadMessage();
-            hasJumped = true;
-          },
-        );
+
+        await act(async () => {
+          await renderComponent(
+            { channel, chatClient },
+            ({ channelUnreadUiState, jumpToFirstUnreadMessage }) => {
+              if (hasJumped || !channelUnreadUiState) return;
+              jumpToFirstUnreadMessage();
+              hasJumped = true;
+            },
+          );
+        });
 
         await waitFor(() =>
           expect(loadMessageIntoState).toHaveBeenCalledWith(
@@ -1081,7 +1177,14 @@ describe('Channel', () => {
             channelsData: [
               {
                 messages: [generateMessage()],
-                read: [{ last_read: new Date().toISOString(), last_read_message_id, user }],
+                read: [
+                  {
+                    last_read: new Date().toISOString(),
+                    last_read_message_id,
+                    unread_messages: 1,
+                    user,
+                  },
+                ],
               },
             ],
             customUser: user,
@@ -1128,7 +1231,14 @@ describe('Channel', () => {
           channelsData: [
             {
               messages: [generateMessage()],
-              read: [{ last_read: new Date().toISOString(), last_read_message_id, user }],
+              read: [
+                {
+                  last_read: new Date().toISOString(),
+                  last_read_message_id,
+                  unread_messages: 1,
+                  user,
+                },
+              ],
             },
           ],
           customUser: user,
@@ -1184,7 +1294,12 @@ describe('Channel', () => {
             {
               messages,
               read: [
-                { last_read: lastReadMessage.created_at.toISOString(), last_read_message_id, user },
+                {
+                  last_read: lastReadMessage.created_at.toISOString(),
+                  last_read_message_id,
+                  unread_messages: 1,
+                  user,
+                },
               ],
             },
           ],
@@ -1223,7 +1338,12 @@ describe('Channel', () => {
             {
               messages,
               read: [
-                { last_read: lastReadMessage.created_at.toISOString(), last_read_message_id, user },
+                {
+                  last_read: lastReadMessage.created_at.toISOString(),
+                  last_read_message_id,
+                  unread_messages: 1,
+                  user,
+                },
               ],
             },
           ],
diff --git a/src/components/ChannelList/__tests__/ChannelList.test.js b/src/components/ChannelList/__tests__/ChannelList.test.js
index 2552dc7849..d903ca077a 100644
--- a/src/components/ChannelList/__tests__/ChannelList.test.js
+++ b/src/components/ChannelList/__tests__/ChannelList.test.js
@@ -113,29 +113,28 @@ describe('ChannelList', () => {
       useMockedApis(chatClient, [queryChannelsApi([])]);
     });
     it('should call `closeMobileNav` prop function, when clicked outside ChannelList', async () => {
-      let result;
-      await act(() => {
-        result = render(
-          <ChatContext.Provider
-            value={{
-              channelsQueryState: channelsQueryStateMock,
-              client: chatClient,
-              closeMobileNav,
-              navOpen: true,
-            }}
-          >
-            <ChannelList {...props} />
-            <div data-testid='outside-channellist' />
-          </ChatContext.Provider>,
-        );
-      });
-      const { container, getByRole, getByTestId } = result;
+      const { container, getByRole, getByTestId } = await render(
+        <ChatContext.Provider
+          value={{
+            channelsQueryState: channelsQueryStateMock,
+            client: chatClient,
+            closeMobileNav,
+            navOpen: true,
+          }}
+        >
+          <ChannelList {...props} />
+          <div data-testid='outside-channellist' />
+        </ChatContext.Provider>,
+      );
       // Wait for list of channels to load in DOM.
       await waitFor(() => {
         expect(getByRole('list')).toBeInTheDocument();
       });
 
-      fireEvent.click(getByTestId('outside-channellist'));
+      await act(() => {
+        fireEvent.click(getByTestId('outside-channellist'));
+      });
+
       await waitFor(() => {
         expect(closeMobileNav).toHaveBeenCalledTimes(1);
       });
@@ -144,30 +143,28 @@ describe('ChannelList', () => {
     });
 
     it('should not call `closeMobileNav` prop function on click, if ChannelList is collapsed', async () => {
-      let result;
-      await act(() => {
-        result = render(
-          <ChatContext.Provider
-            value={{
-              channelsQueryState: channelsQueryStateMock,
-              client: chatClient,
-              closeMobileNav,
-              navOpen: false,
-            }}
-          >
-            <ChannelList {...props} />
-            <div data-testid='outside-channellist' />
-          </ChatContext.Provider>,
-        );
-      });
-      const { container, getByRole, getByTestId } = result;
+      const { container, getByRole, getByTestId } = await render(
+        <ChatContext.Provider
+          value={{
+            channelsQueryState: channelsQueryStateMock,
+            client: chatClient,
+            closeMobileNav,
+            navOpen: false,
+          }}
+        >
+          <ChannelList {...props} />
+          <div data-testid='outside-channellist' />
+        </ChatContext.Provider>,
+      );
 
       // Wait for list of channels to load in DOM.
       await waitFor(() => {
         expect(getByRole('list')).toBeInTheDocument();
       });
 
-      fireEvent.click(getByTestId('outside-channellist'));
+      await act(() => {
+        fireEvent.click(getByTestId('outside-channellist'));
+      });
       await waitFor(() => {
         expect(closeMobileNav).toHaveBeenCalledTimes(0);
       });
@@ -224,48 +221,52 @@ describe('ChannelList', () => {
     };
     const queryChannelsMock = jest.spyOn(client, 'queryChannels').mockImplementationOnce();
 
-    let rerender;
-    await act(async () => {
-      const result = await render(
-        <Chat client={client}>
-          <ChannelList {...props} />
-        </Chat>,
+    const { rerender } = render(
+      <Chat client={client}>
+        <ChannelList {...props} />
+      </Chat>,
+    );
+
+    await waitFor(() => {
+      expect(queryChannelsMock).toHaveBeenCalledTimes(0);
+      expect(props.customQueryChannels).toHaveBeenCalledTimes(1);
+    });
+    await waitFor(() => {
+      expect(props.customQueryChannels).toHaveBeenCalledWith(
+        expect.objectContaining({
+          currentChannels: [],
+          queryType: 'reload',
+          setChannels: expect.any(Function),
+          setHasNextPage: expect.any(Function),
+        }),
       );
-      rerender = result.rerender;
     });
 
-    expect(queryChannelsMock).toHaveBeenCalledTimes(0);
-    expect(props.customQueryChannels).toHaveBeenCalledTimes(1);
-    expect(props.customQueryChannels).toHaveBeenCalledWith(
-      expect.objectContaining({
-        currentChannels: [],
-        queryType: 'reload',
-        setChannels: expect.any(Function),
-        setHasNextPage: expect.any(Function),
-      }),
+    rerender(
+      <Chat client={client}>
+        <ChannelList {...props} />
+      </Chat>,
     );
 
-    await act(async () => {
-      await rerender(
-        <Chat client={client}>
-          <ChannelList {...props} />
-        </Chat>,
-      );
-    });
-    await act(() => {
+    act(() => {
       fireEvent.click(screen.getByTestId('load-more-button'));
     });
 
-    expect(queryChannelsMock).toHaveBeenCalledTimes(0);
-    expect(props.customQueryChannels).toHaveBeenCalledTimes(2);
-    expect(props.customQueryChannels).toHaveBeenCalledWith(
-      expect.objectContaining({
-        currentChannels: [channels[0]],
-        queryType: 'load-more',
-        setChannels: expect.any(Function),
-        setHasNextPage: expect.any(Function),
-      }),
-    );
+    await waitFor(() => {
+      expect(queryChannelsMock).toHaveBeenCalledTimes(0);
+      expect(props.customQueryChannels).toHaveBeenCalledTimes(2);
+    });
+    await waitFor(() => {
+      expect(props.customQueryChannels).toHaveBeenCalledWith(
+        expect.objectContaining({
+          currentChannels: [channels[0]],
+          queryType: 'load-more',
+          setChannels: expect.any(Function),
+          setHasNextPage: expect.any(Function),
+        }),
+      );
+    });
+
     queryChannelsMock.mockRestore();
   });
 
@@ -339,24 +340,24 @@ describe('ChannelList', () => {
       return <ChannelListMessenger {...props} />;
     };
 
-    await act(() => {
-      render(
-        <Chat client={chatClient}>
-          <QueryStateInterceptor>
-            <ChannelList List={ChannelListMessengerPropsInterceptor} />
-          </QueryStateInterceptor>
-        </Chat>,
-      );
-    });
+    await render(
+      <Chat client={chatClient}>
+        <QueryStateInterceptor>
+          <ChannelList List={ChannelListMessengerPropsInterceptor} />
+        </QueryStateInterceptor>
+      </Chat>,
+    );
 
-    expect(channelsQueryStatesHistory).toHaveLength(3);
-    expect(channelListMessengerLoadingHistory).toHaveLength(3);
-    expect(channelsQueryStatesHistory[0]).toBe('uninitialized');
-    expect(channelListMessengerLoadingHistory[0]).toBe(true);
-    expect(channelsQueryStatesHistory[1]).toBe('reload');
-    expect(channelListMessengerLoadingHistory[1]).toBe(true);
-    expect(channelsQueryStatesHistory[2]).toBeNull();
-    expect(channelListMessengerLoadingHistory[2]).toBe(false);
+    await waitFor(() => {
+      expect(channelsQueryStatesHistory).toHaveLength(3);
+      expect(channelListMessengerLoadingHistory).toHaveLength(3);
+      expect(channelsQueryStatesHistory[0]).toBe('uninitialized');
+      expect(channelListMessengerLoadingHistory[0]).toBe(true);
+      expect(channelsQueryStatesHistory[1]).toBe('reload');
+      expect(channelListMessengerLoadingHistory[1]).toBe(true);
+      expect(channelsQueryStatesHistory[2]).toBeNull();
+      expect(channelListMessengerLoadingHistory[2]).toBe(false);
+    });
   });
 
   it('ChannelPreview UI components should render `Avatar` when the custom prop is provided', async () => {
@@ -878,12 +879,12 @@ describe('ChannelList', () => {
         List: ChannelListComponent,
         Preview: ChannelPreviewComponent,
       };
-      const sendNewMessageOnChannel3 = () => {
+      const sendNewMessageOnChannel3 = async () => {
         const newMessage = generateMessage({
           user: generateUser(),
         });
 
-        act(() => dispatchMessageNewEvent(chatClient, newMessage, testChannel3.channel));
+        await act(() => dispatchMessageNewEvent(chatClient, newMessage, testChannel3.channel));
         return newMessage;
       };
 
@@ -903,7 +904,7 @@ describe('ChannelList', () => {
           expect(getByRole('list')).toBeInTheDocument();
         });
 
-        const newMessage = sendNewMessageOnChannel3();
+        const newMessage = await sendNewMessageOnChannel3();
         await waitFor(() => {
           expect(getByText(newMessage.text)).toBeInTheDocument();
         });
@@ -929,7 +930,7 @@ describe('ChannelList', () => {
           expect(getByRole('list')).toBeInTheDocument();
         });
 
-        const newMessage = sendNewMessageOnChannel3();
+        const newMessage = await sendNewMessageOnChannel3();
 
         await waitFor(() => {
           expect(getByText(newMessage.text)).toBeInTheDocument();
@@ -946,14 +947,12 @@ describe('ChannelList', () => {
 
       it('should execute custom event handler', async () => {
         const onMessageNewEvent = jest.fn();
-        await act(() => {
-          render(
-            <Chat client={chatClient}>
-              <ChannelList {...props} onMessageNewHandler={onMessageNewEvent} />
-            </Chat>,
-          );
-        });
-        const message = sendNewMessageOnChannel3();
+        render(
+          <Chat client={chatClient}>
+            <ChannelList {...props} onMessageNewHandler={onMessageNewEvent} />
+          </Chat>,
+        );
+        const message = await sendNewMessageOnChannel3();
         await waitFor(() => {
           expect(onMessageNewEvent.mock.calls[0][0]).toStrictEqual(expect.any(Function));
           expect(onMessageNewEvent.mock.calls[0][1]).toStrictEqual(
@@ -1559,7 +1558,9 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(1);
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(1);
+      });
     });
 
     it('should reload the channels on connection.recovered', async () => {
@@ -1573,10 +1574,12 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
-      expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+        expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
     });
 
     it('should execute recovery queries outside throttle interval', async () => {
@@ -1594,10 +1597,12 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
-      expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+        expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
       await act(() => {
         chatClient.dispatchEvent({
@@ -1605,10 +1610,12 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
-      expect(chatClient.queryChannels.mock.calls[2][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
+        expect(chatClient.queryChannels.mock.calls[2][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
       dateNowSpy.mockRestore();
     });
@@ -1622,22 +1629,24 @@ describe('ChannelList', () => {
         .mockReturnValueOnce(1)
         .mockReturnValueOnce(RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS);
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
-      expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+        expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
       dateNowSpy.mockRestore();
     });
@@ -1659,7 +1668,9 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+      });
 
       await act(() => {
         chatClient.dispatchEvent({
@@ -1667,10 +1678,12 @@ describe('ChannelList', () => {
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
-      expect(chatClient.queryChannels.mock.calls[2][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
+        expect(chatClient.queryChannels.mock.calls[2][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
       dateNowSpy.mockRestore();
     });
@@ -1687,30 +1700,38 @@ describe('ChannelList', () => {
         .mockReturnValueOnce(MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS)
         .mockReturnValueOnce(MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS + 1);
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
-      expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+        expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
 
-      await act(() => {
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+      });
+
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
+
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
+      });
       dateNowSpy.mockRestore();
     });
 
@@ -1726,32 +1747,38 @@ describe('ChannelList', () => {
         .mockReturnValueOnce(recoveryThrottleIntervalMs + 1)
         .mockReturnValueOnce(MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS + 1);
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
-      expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
-        expect.objectContaining({ offset: 0 }),
-      );
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+        expect(chatClient.queryChannels.mock.calls[1][2]).toStrictEqual(
+          expect.objectContaining({ offset: 0 }),
+        );
+      });
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
 
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(2);
+      });
 
-      await act(() => {
+      act(() => {
         chatClient.dispatchEvent({
           type: 'connection.recovered',
         });
       });
-      expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
 
+      await waitFor(() => {
+        expect(chatClient.queryChannels).toHaveBeenCalledTimes(3);
+      });
       dateNowSpy.mockRestore();
     });
   });
@@ -1780,23 +1807,26 @@ describe('ChannelList', () => {
 
       useMockedApis(chatClient, [queryChannelsApi(channelsToBeLoaded)]);
 
-      await act(async () => {
-        await render(
-          <Chat client={chatClient}>
-            <ChannelList {...props} />
-          </Chat>,
-        );
-      });
+      await render(
+        <Chat client={chatClient}>
+          <ChannelList {...props} />
+        </Chat>,
+      );
 
-      expect(screen.getByText(channelsDataToIdString(channelsToBeLoaded))).toBeInTheDocument();
+      await waitFor(() => {
+        expect(screen.getByText(channelsDataToIdString(channelsToBeLoaded))).toBeInTheDocument();
+      });
 
       await act(() => {
         setChannelsFromOutside(chatClient.hydrateActiveChannels(channelsToBeSet));
       });
-      expect(
-        screen.queryByText(channelsDataToIdString(channelsToBeLoaded)),
-      ).not.toBeInTheDocument();
-      expect(screen.getByText(channelsDataToIdString(channelsToBeSet))).toBeInTheDocument();
+
+      await waitFor(() => {
+        expect(
+          screen.queryByText(channelsDataToIdString(channelsToBeLoaded)),
+        ).not.toBeInTheDocument();
+        expect(screen.getByText(channelsDataToIdString(channelsToBeSet))).toBeInTheDocument();
+      });
     });
   });
 });
diff --git a/src/components/ChannelList/hooks/usePaginatedChannels.ts b/src/components/ChannelList/hooks/usePaginatedChannels.ts
index b85ac1a571..ad79cdaa94 100644
--- a/src/components/ChannelList/hooks/usePaginatedChannels.ts
+++ b/src/components/ChannelList/hooks/usePaginatedChannels.ts
@@ -52,9 +52,7 @@ export const usePaginatedChannels = <
   const recoveryThrottleInterval =
     recoveryThrottleIntervalMs < MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS
       ? MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS
-      : recoveryThrottleIntervalMs
-      ? recoveryThrottleIntervalMs
-      : RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS;
+      : recoveryThrottleIntervalMs ?? RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS;
   // memoize props
   const filterString = useMemo(() => JSON.stringify(filters), [filters]);
   const sortString = useMemo(() => JSON.stringify(sort), [sort]);
diff --git a/src/components/ChannelPreview/ChannelPreview.tsx b/src/components/ChannelPreview/ChannelPreview.tsx
index 7697935b7a..8b17bc555b 100644
--- a/src/components/ChannelPreview/ChannelPreview.tsx
+++ b/src/components/ChannelPreview/ChannelPreview.tsx
@@ -1,4 +1,5 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import throttle from 'lodash.throttle';
+import React, { useEffect, useMemo, useState } from 'react';
 
 import { ChannelPreviewMessenger } from './ChannelPreviewMessenger';
 import { useIsChannelMuted } from './hooks/useIsChannelMuted';
@@ -107,13 +108,17 @@ export const ChannelPreview = <
     };
   }, [channel, client]);
 
-  const refreshUnreadCount = useCallback(() => {
-    if (muted) {
-      setUnread(0);
-    } else {
-      setUnread(channel.countUnread());
-    }
-  }, [channel, muted]);
+  const refreshUnreadCount = useMemo(
+    () =>
+      throttle(() => {
+        if (muted) {
+          setUnread(0);
+        } else {
+          setUnread(channel.countUnread());
+        }
+      }, 400),
+    [channel, muted],
+  );
 
   useEffect(() => {
     refreshUnreadCount();
diff --git a/src/components/MessageList/MessageList.tsx b/src/components/MessageList/MessageList.tsx
index 62d70f07a7..60b1c47373 100644
--- a/src/components/MessageList/MessageList.tsx
+++ b/src/components/MessageList/MessageList.tsx
@@ -67,12 +67,12 @@ const MessageListWithContext = <
     unsafeHTML = false,
     headerPosition,
     read,
-    markReadOnScrolledToBottom,
     renderMessages = defaultRenderMessages,
     messageLimit = 100,
     loadMore: loadMoreCallback,
     loadMoreNewer: loadMoreNewerCallback,
     hasMoreNewer = false,
+    showUnreadNotificationAlways,
     suppressAutoscroll,
     highlightedMessageId,
     jumpToLatestMessage = () => Promise.resolve(),
@@ -111,14 +111,15 @@ const MessageListWithContext = <
 
   const { show: showUnreadMessagesNotification } = useUnreadMessagesNotification({
     isMessageListScrolledToBottom,
+    showAlways: !!showUnreadNotificationAlways,
     unreadCount: channelUnreadUiState?.unread_messages,
   });
 
   useMarkRead({
     isMessageListScrolledToBottom,
-    markReadOnScrolledToBottom,
     messageListIsThread: threadList,
     unreadCount: channelUnreadUiState?.unread_messages ?? 0,
+    wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
   });
 
   const { messageGroupStyles, messages: enrichedMessages } = useEnrichedMessages({
@@ -326,8 +327,6 @@ export type MessageListProps<
   loadMore?: ChannelActionContextValue['loadMore'] | (() => Promise<void>);
   /** Function called when newer messages are to be loaded, defaults to function stored in [ChannelActionContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_action_context/) */
   loadMoreNewer?: ChannelActionContextValue['loadMoreNewer'] | (() => Promise<void>);
-  /** When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list. */
-  markReadOnScrolledToBottom?: boolean;
   /** The limit to use when paginating messages */
   messageLimit?: number;
   /** The messages to render in the list, defaults to messages stored in [ChannelStateContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_state_context/) */
@@ -344,6 +343,12 @@ export type MessageListProps<
    * Defaults to 200px
    */
   scrolledUpThreshold?: number;
+  /**
+   * The floating notification informing about unread messages will be shown when the
+   * UnreadMessagesSeparator is not visible. The default is false, that means the notification
+   * is shown only when viewing unread messages.
+   */
+  showUnreadNotificationAlways?: boolean;
   /** If true, indicates the message list is a thread  */
   threadList?: boolean; // todo: refactor needed - message list should have a state in which among others it would be optionally flagged as thread
 };
diff --git a/src/components/MessageList/ScrollToBottomButton.tsx b/src/components/MessageList/ScrollToBottomButton.tsx
index 28179f2d30..8255f4a00e 100644
--- a/src/components/MessageList/ScrollToBottomButton.tsx
+++ b/src/components/MessageList/ScrollToBottomButton.tsx
@@ -1,17 +1,65 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import clsx from 'clsx';
 
 import { ArrowDown } from './icons';
 
+import { useChannelStateContext, useChatContext } from '../../context';
+
+import type { Event } from 'stream-chat';
 import type { MessageNotificationProps } from './MessageNotification';
 
 const UnMemoizedScrollToBottomButton = (
-  props: Pick<
-    MessageNotificationProps,
-    'isMessageListScrolledToBottom' | 'onClick' | 'threadList' | 'unreadCount'
-  >,
+  props: Pick<MessageNotificationProps, 'isMessageListScrolledToBottom' | 'onClick' | 'threadList'>,
 ) => {
-  const { isMessageListScrolledToBottom, onClick, unreadCount = 0 } = props;
+  const { isMessageListScrolledToBottom, onClick, threadList } = props;
+
+  const { channel: activeChannel, client } = useChatContext();
+  const { thread } = useChannelStateContext();
+  const [countUnread, setCountUnread] = useState(activeChannel?.countUnread() || 0);
+  const [replyCount, setReplyCount] = useState(thread?.reply_count || 0);
+  const observedEvent = threadList ? 'message.updated' : 'message.new';
+
+  useEffect(() => {
+    const handleEvent = (event: Event) => {
+      const newMessageInAnotherChannel = event.cid !== activeChannel?.cid;
+      const newMessageIsMine = event.user?.id === client.user?.id;
+
+      const isThreadOpen = !!thread;
+      const newMessageIsReply = !!event.message?.parent_id;
+      const dontIncreaseMainListCounterOnNewReply =
+        isThreadOpen && !threadList && newMessageIsReply;
+
+      if (
+        isMessageListScrolledToBottom ||
+        newMessageInAnotherChannel ||
+        newMessageIsMine ||
+        dontIncreaseMainListCounterOnNewReply
+      ) {
+        return;
+      }
+
+      if (event.type === 'message.new') {
+        // cannot rely on channel.countUnread because active channel is automatically marked read
+        setCountUnread((prev) => prev + 1);
+      } else if (event.message?.id === thread?.id) {
+        const newReplyCount = event.message?.reply_count || 0;
+        setCountUnread(() => newReplyCount - replyCount);
+      }
+    };
+    client.on(observedEvent, handleEvent);
+
+    return () => {
+      client.off(observedEvent, handleEvent);
+    };
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [activeChannel, isMessageListScrolledToBottom, observedEvent, replyCount, thread]);
+
+  useEffect(() => {
+    if (isMessageListScrolledToBottom) {
+      setCountUnread(0);
+      setReplyCount(thread?.reply_count || 0);
+    }
+  }, [isMessageListScrolledToBottom, thread]);
 
   if (isMessageListScrolledToBottom) return null;
 
@@ -28,7 +76,7 @@ const UnMemoizedScrollToBottomButton = (
         onClick={onClick}
       >
         <ArrowDown />
-        {unreadCount > 0 && (
+        {countUnread > 0 && (
           <div
             className={clsx(
               'str-chat__message-notification',
@@ -37,7 +85,7 @@ const UnMemoizedScrollToBottomButton = (
             )}
             data-testid={'unread-message-notification-counter'}
           >
-            {unreadCount}
+            {countUnread}
           </div>
         )}
       </button>
diff --git a/src/components/MessageList/UnreadMessagesNotification.tsx b/src/components/MessageList/UnreadMessagesNotification.tsx
index 5c7f6135b2..3f3117e767 100644
--- a/src/components/MessageList/UnreadMessagesNotification.tsx
+++ b/src/components/MessageList/UnreadMessagesNotification.tsx
@@ -3,12 +3,23 @@ import { CloseIcon } from './icons';
 import { useChannelActionContext, useTranslationContext } from '../../context';
 
 export type UnreadMessagesNotificationProps = {
+  /**
+   * Configuration parameter to determine the message page size, when jumping to the first unread message.
+   */
   queryMessageLimit?: number;
+  /**
+   * Configuration parameter to determine, whether the unread count is to be shown on the component. Disabled by default.
+   */
+  showCount?: boolean;
+  /**
+   * The count of unread messages to be displayed if enabled.
+   */
   unreadCount?: number;
 };
 
 export const UnreadMessagesNotification = ({
   queryMessageLimit,
+  showCount,
   unreadCount,
 }: UnreadMessagesNotificationProps) => {
   const { jumpToFirstUnreadMessage, markRead } = useChannelActionContext(
@@ -22,7 +33,9 @@ export const UnreadMessagesNotification = ({
       data-testid='unread-messages-notification'
     >
       <button onClick={() => jumpToFirstUnreadMessage(queryMessageLimit)}>
-        {t<string>('{{count}} unread', { count: unreadCount ?? 0 })}
+        {unreadCount && showCount
+          ? t<string>('{{count}} unread', { count: unreadCount ?? 0 })
+          : t<string>('Unread messages')}
       </button>
       <button onClick={() => markRead()}>
         <CloseIcon />
diff --git a/src/components/MessageList/UnreadMessagesSeparator.tsx b/src/components/MessageList/UnreadMessagesSeparator.tsx
index f2948e713e..0ea91c7f29 100644
--- a/src/components/MessageList/UnreadMessagesSeparator.tsx
+++ b/src/components/MessageList/UnreadMessagesSeparator.tsx
@@ -4,14 +4,24 @@ import { useTranslationContext } from '../../context';
 export const UNREAD_MESSAGE_SEPARATOR_CLASS = 'str-chat__unread-messages-separator';
 
 export type UnreadMessagesSeparatorProps = {
+  /**
+   * Configuration parameter to determine, whether the unread count is to be shown on the component. Disabled by default.
+   */
+  showCount?: boolean;
+  /**
+   * The count of unread messages to be displayed if enabled.
+   */
   unreadCount?: number;
 };
 
-export const UnreadMessagesSeparator = ({ unreadCount }: UnreadMessagesSeparatorProps) => {
+export const UnreadMessagesSeparator = ({
+  showCount,
+  unreadCount,
+}: UnreadMessagesSeparatorProps) => {
   const { t } = useTranslationContext('UnreadMessagesSeparator');
   return (
     <div className={UNREAD_MESSAGE_SEPARATOR_CLASS} data-testid='unread-messages-separator'>
-      {unreadCount
+      {unreadCount && showCount
         ? t<string>('unreadMessagesSeparatorText', { count: unreadCount })
         : t<string>('Unread messages')}
     </div>
diff --git a/src/components/MessageList/VirtualizedMessageList.tsx b/src/components/MessageList/VirtualizedMessageList.tsx
index 62442f1026..df0da69242 100644
--- a/src/components/MessageList/VirtualizedMessageList.tsx
+++ b/src/components/MessageList/VirtualizedMessageList.tsx
@@ -180,7 +180,6 @@ const VirtualizedMessageListWithContext = <
     loadingMore,
     loadMore,
     loadMoreNewer,
-    markReadOnScrolledToBottom,
     Message: MessageUIComponentFromProps,
     messageActions,
     messageLimit = 100,
@@ -194,6 +193,7 @@ const VirtualizedMessageListWithContext = <
     scrollToLatestMessageOnFocus = false,
     separateGiphyPreview = false,
     shouldGroupByUser = false,
+    showUnreadNotificationAlways,
     stickToBottomScrollBehavior = 'smooth',
     suppressAutoscroll,
     threadList,
@@ -231,6 +231,7 @@ const VirtualizedMessageListWithContext = <
     toggleShowUnreadMessagesNotification,
   } = useUnreadMessagesNotificationVirtualized({
     lastRead: channelUnreadUiState?.last_read,
+    showAlways: !!showUnreadNotificationAlways,
     unreadCount: channelUnreadUiState?.unread_messages ?? 0,
   });
 
@@ -312,9 +313,9 @@ const VirtualizedMessageListWithContext = <
 
   useMarkRead({
     isMessageListScrolledToBottom,
-    markReadOnScrolledToBottom,
     messageListIsThread: !!threadList,
     unreadCount: channelUnreadUiState?.unread_messages ?? 0,
+    wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
   });
 
   const scrollToBottom = useCallback(async () => {
@@ -536,8 +537,6 @@ export type VirtualizedMessageListProps<
   loadMore?: ChannelActionContextValue['loadMore'] | (() => Promise<void>);
   /** Function called when new messages are to be loaded, defaults to function stored in [ChannelActionContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_action_context/) */
   loadMoreNewer?: ChannelActionContextValue['loadMore'] | (() => Promise<void>);
-  /** When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list. */
-  markReadOnScrolledToBottom?: boolean;
   /** Custom UI component to display a message, defaults to and accepts same props as [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */
   Message?: React.ComponentType<MessageUIComponentProps<StreamChatGenerics>>;
   /** The limit to use when paginating messages */
@@ -573,6 +572,12 @@ export type VirtualizedMessageListProps<
   separateGiphyPreview?: boolean;
   /** If true, group messages belonging to the same user, otherwise show each message individually */
   shouldGroupByUser?: boolean;
+  /**
+   * The floating notification informing about unread messages will be shown when the
+   * UnreadMessagesSeparator is not visible. The default is false, that means the notification
+   * is shown only when viewing unread messages.
+   */
+  showUnreadNotificationAlways?: boolean;
   /** The scrollTo behavior when new messages appear. Use `"smooth"` for regular chat channels, and `"auto"` (which results in instant scroll to bottom) if you expect high throughput. */
   stickToBottomScrollBehavior?: 'smooth' | 'auto';
   /** stops the list from autoscrolling when new messages are loaded */
diff --git a/src/components/MessageList/__tests__/MessageList.test.js b/src/components/MessageList/__tests__/MessageList.test.js
index eeeb9c76b0..42ae3b2baf 100644
--- a/src/components/MessageList/__tests__/MessageList.test.js
+++ b/src/components/MessageList/__tests__/MessageList.test.js
@@ -315,7 +315,7 @@ describe('MessageList', () => {
     const messages = Array.from({ length: 5 }, generateMessage);
     const unread_messages = 2;
     const lastReadMessage = messages[unread_messages];
-    const separatorText = `${unread_messages} unread messages`;
+    const separatorText = `Unread messages`;
     const dispatchMarkUnreadForChannel = ({ channel, client, payload = {} }) => {
       dispatchNotificationMarkUnread({
         channel,
@@ -388,7 +388,7 @@ describe('MessageList', () => {
         });
       });
 
-      expect(markReadMock).toHaveBeenCalledTimes(1);
+      expect(markReadMock).toHaveBeenCalledTimes(2);
       expect(screen.queryByText(separatorText)).toBeInTheDocument();
     });
 
@@ -522,9 +522,12 @@ describe('MessageList', () => {
 
     describe('notification', () => {
       const chatContext = { themeVersion: '2' };
-      const notificationText = `${unread_messages} unread`;
+      const UNREAD_MESSAGES_NOTIFICATION_TEST_ID = 'unread-messages-notification';
       const observerEntriesScrolledBelowSeparator = [
-        { boundingClientRect: { top: 10 }, isIntersecting: false, rootBounds: { bottom: 11 } },
+        { boundingClientRect: { bottom: -1 }, isIntersecting: false },
+      ];
+      const observerEntriesScrolledAboveSeparator = [
+        { boundingClientRect: { bottom: 1 }, isIntersecting: false },
       ];
 
       const setupTest = async ({
@@ -558,22 +561,62 @@ describe('MessageList', () => {
 
       it('should not display unread messages notification when scrolled to unread messages separator', async () => {
         await setupTest({ entries: [{ isIntersecting: true }] });
-        expect(screen.queryByText(notificationText)).not.toBeInTheDocument();
-      });
-
-      it("should not display unread messages notification when unread messages separator top edge is above container's bottom", async () => {
-        await setupTest({
-          entries: [
-            { boundingClientRect: { top: 11 }, isIntersecting: false, rootBounds: { bottom: 10 } },
-          ],
-        });
-        expect(screen.queryByText(notificationText)).not.toBeInTheDocument();
+        expect(screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID)).not.toBeInTheDocument();
       });
 
-      it("should display unread messages notification when unread messages separator top edge is below container's bottom", async () => {
-        await setupTest({ entries: observerEntriesScrolledBelowSeparator });
-        expect(screen.getByText(notificationText)).toBeInTheDocument();
-      });
+      it.each([
+        [
+          'should not',
+          "top edge is below container's visible bottom",
+          observerEntriesScrolledAboveSeparator,
+          undefined,
+        ],
+        [
+          'should',
+          "bottom edge is above container's visible top",
+          observerEntriesScrolledBelowSeparator,
+          undefined,
+        ],
+        [
+          'should',
+          "top edge is below container's visible bottom when showUnreadNotificationAlways enabled",
+          observerEntriesScrolledAboveSeparator,
+          { showUnreadNotificationAlways: true },
+        ],
+        [
+          'should not',
+          "top edge is below container's visible bottom when showUnreadNotificationAlways disabled",
+          observerEntriesScrolledAboveSeparator,
+          { showUnreadNotificationAlways: false },
+        ],
+        [
+          'should',
+          "bottom edge is above container's visible top when showUnreadNotificationAlways disabled",
+          observerEntriesScrolledBelowSeparator,
+          { showUnreadNotificationAlways: false },
+        ],
+        [
+          'should',
+          "bottom edge is above container's visible top when showUnreadNotificationAlways enabled",
+          observerEntriesScrolledBelowSeparator,
+          { showUnreadNotificationAlways: true },
+        ],
+      ])(
+        '%s display unread messages notification when unread messages separator %s',
+        async (expected, __, entries, msgListProps) => {
+          await setupTest({
+            entries,
+            msgListProps,
+          });
+          if (expected === 'should') {
+            expect(screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID)).toBeInTheDocument();
+          } else {
+            expect(
+              screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID),
+            ).not.toBeInTheDocument();
+          }
+        },
+      );
 
       it('should display custom unread messages notification', async () => {
         const customUnreadMessagesNotificationText = 'customUnreadMessagesNotificationText';
@@ -591,13 +634,13 @@ describe('MessageList', () => {
           dispatchMarkUnreadPayload: { unread_messages: 0 },
           entries: observerEntriesScrolledBelowSeparator,
         });
-        expect(screen.queryByText(notificationText)).not.toBeInTheDocument();
+        expect(screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID)).not.toBeInTheDocument();
       });
 
       it('should not display unread messages notification IntersectionObserver is undefined', async () => {
         window.IntersectionObserver = undefined;
         await setupTest({ entries: observerEntriesScrolledBelowSeparator });
-        expect(screen.queryByText(notificationText)).not.toBeInTheDocument();
+        expect(screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID)).not.toBeInTheDocument();
       });
 
       it('should not display unread messages notification in thread', async () => {
@@ -605,7 +648,7 @@ describe('MessageList', () => {
           entries: observerEntriesScrolledBelowSeparator,
           msgListProps: { threadList: true },
         });
-        expect(screen.queryByText(notificationText)).not.toBeInTheDocument();
+        expect(screen.queryByTestId(UNREAD_MESSAGES_NOTIFICATION_TEST_ID)).not.toBeInTheDocument();
       });
     });
 
@@ -616,7 +659,7 @@ describe('MessageList', () => {
         <MessageListNotifications {...props} isMessageListScrolledToBottom={false} />
       );
 
-      it('reflects the channel unread state', async () => {
+      it('does not reflect the channel unread  UI state', async () => {
         const {
           channels: [channel],
           client,
@@ -641,9 +684,7 @@ describe('MessageList', () => {
           dispatchMarkUnreadForChannel({ channel, client });
         });
 
-        expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).toHaveTextContent(
-          unread_messages,
-        );
+        expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).not.toBeInTheDocument();
       });
 
       it('does not reflect the channel unread state in a thread', async () => {
diff --git a/src/components/MessageList/__tests__/ScrollToBottomButton.test.js b/src/components/MessageList/__tests__/ScrollToBottomButton.test.js
index 896c31397a..8901acce96 100644
--- a/src/components/MessageList/__tests__/ScrollToBottomButton.test.js
+++ b/src/components/MessageList/__tests__/ScrollToBottomButton.test.js
@@ -4,24 +4,52 @@ import '@testing-library/jest-dom';
 
 import { ScrollToBottomButton } from '../ScrollToBottomButton';
 import { ChannelStateProvider, ChatProvider } from '../../../context';
-import { createClientWithChannel } from '../../../mock-builders';
+import {
+  createClientWithChannel,
+  dispatchMessageNewEvent,
+  dispatchMessageUpdatedEvent,
+  generateMessage,
+} from '../../../mock-builders';
 
 const BUTTON_TEST_ID = 'message-notification';
 const NEW_MESSAGE_COUNTER_TEST_ID = 'unread-message-notification-counter';
 
+const mainList = 'the main message list';
+const threadList = 'a thread';
+
 let client;
 let channel;
+let users;
 let containerIsThread;
+let anotherUser;
 let channelStateContext;
 let parentMsg;
 
 const onClick = jest.fn();
 
-describe('ScrollToBottomButton', () => {
+const dispatchMessageEvents = ({ channel, client, newMessage, parentMsg, user }) => {
+  if (containerIsThread) {
+    dispatchMessageUpdatedEvent(
+      client,
+      { ...parentMsg, reply_count: parentMsg.reply_count + 1 },
+      channel,
+      user,
+    );
+  }
+  dispatchMessageNewEvent(client, newMessage, channel);
+};
+
+describe.each([
+  [mainList, threadList],
+  [threadList, mainList],
+])('ScrollToBottomButton in %s', (containerMsgList, otherMsgList) => {
   beforeEach(async () => {
     const result = await createClientWithChannel();
     client = result.client;
     channel = result.channel;
+    users = result.users;
+    containerIsThread = containerMsgList === threadList;
+    anotherUser = Object.values(channel.state.members).find((u) => u.id !== client.user.id);
     parentMsg = { ...Object.values(channel.state.messages)[0], reply_count: 0 };
     channelStateContext = {
       thread: containerIsThread ? parentMsg : null,
@@ -30,7 +58,7 @@ describe('ScrollToBottomButton', () => {
 
   afterEach(jest.clearAllMocks);
 
-  it(`is not rendered if the container is scrolled to the bottom`, () => {
+  it(`is not rendered if ${containerMsgList} scrolled to the bottom`, () => {
     const { container } = render(
       <ChatProvider value={{ channel, client }}>
         <ChannelStateProvider value={channelStateContext}>
@@ -71,34 +99,204 @@ describe('ScrollToBottomButton', () => {
     });
   });
 
-  it('reflects the unread count from props', async () => {
-    const { rerender } = render(
+  it('does not increase the unread count if already scrolled at the bottom', async () => {
+    const newMessage = generateMessage({ user: anotherUser });
+    render(
       <ChatProvider value={{ channel, client }}>
         <ChannelStateProvider value={channelStateContext}>
-          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+          <ScrollToBottomButton isMessageListScrolledToBottom onClick={onClick} />
         </ChannelStateProvider>
       </ChatProvider>,
     );
 
-    expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).not.toBeInTheDocument();
+    await act(() => {
+      dispatchMessageEvents({
+        channel,
+        client,
+        newMessage,
+        parentMsg,
+        user: anotherUser,
+      });
+    });
+
+    await waitFor(() => {
+      const counter = screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID);
+      expect(counter).not.toBeInTheDocument();
+    });
+  });
 
-    const unreadCount = 2;
-    rerender(
+  it('shows the count unread if new message arrives to active channel from another user', async () => {
+    const newMessage = generateMessage({ user: anotherUser });
+    render(
       <ChatProvider value={{ channel, client }}>
         <ChannelStateProvider value={channelStateContext}>
-          <ScrollToBottomButton
-            isMessageListScrolledToBottom={false}
-            onClick={onClick}
-            unreadCount={unreadCount}
-          />
+          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
         </ChannelStateProvider>
       </ChatProvider>,
     );
 
+    await act(() => {
+      dispatchMessageEvents({
+        channel,
+        client,
+        newMessage,
+        parentMsg,
+        user: anotherUser,
+      });
+    });
+
     await waitFor(() => {
       const counter = screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID);
       expect(counter).toBeInTheDocument();
-      expect(counter).toHaveTextContent(unreadCount);
+      expect(counter).toHaveTextContent('1');
+    });
+  });
+
+  it('does not show unread count for own arriving messages', async () => {
+    const newMessage = generateMessage({ user: client.user });
+    render(
+      <ChatProvider value={{ channel, client }}>
+        <ChannelStateProvider value={channelStateContext}>
+          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+        </ChannelStateProvider>
+      </ChatProvider>,
+    );
+
+    await act(() => {
+      dispatchMessageEvents({
+        channel,
+        client,
+        newMessage,
+        parentMsg,
+        user: client.user,
+      });
+    });
+
+    await waitFor(() => {
+      expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).not.toBeInTheDocument();
+    });
+  });
+
+  it('does not show unread count for messages from others arriving to non-active channel', async () => {
+    const newMessage = generateMessage({ user: anotherUser });
+    const { channel: nonActiveChannel } = await createClientWithChannel({
+      existingClient: client,
+      existingUsers: users,
+    });
+    render(
+      <ChatProvider value={{ channel, client }}>
+        <ChannelStateProvider value={channelStateContext}>
+          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+        </ChannelStateProvider>
+      </ChatProvider>,
+    );
+
+    await act(() => {
+      dispatchMessageEvents({
+        channel: nonActiveChannel,
+        client,
+        newMessage,
+        parentMsg,
+        user: anotherUser,
+      });
+    });
+
+    await waitFor(() => {
+      expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).not.toBeInTheDocument();
+    });
+  });
+
+  it('does not show unread count for messages arriving from me to non-active channel', async () => {
+    const newMessage = generateMessage({ user: client.user });
+    const { channel: nonActiveChannel } = await createClientWithChannel({
+      existingClient: client,
+      existingUsers: users,
+    });
+
+    render(
+      <ChatProvider value={{ channel, client }}>
+        <ChannelStateProvider value={channelStateContext}>
+          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+        </ChannelStateProvider>
+      </ChatProvider>,
+    );
+
+    await act(() => {
+      dispatchMessageEvents({
+        channel: nonActiveChannel,
+        client,
+        newMessage,
+        parentMsg,
+        user: client.user,
+      });
+    });
+
+    await waitFor(() => {
+      expect(screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID)).not.toBeInTheDocument();
+    });
+  });
+
+  it('increases the count unread with each new message arrival', async () => {
+    render(
+      <ChatProvider value={{ channel, client }}>
+        <ChannelStateProvider value={channelStateContext}>
+          <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+        </ChannelStateProvider>
+      </ChatProvider>,
+    );
+
+    for (let i = 1; i <= 2; i++) {
+      const newMessage = generateMessage({ user: anotherUser });
+      await act(() => {
+        dispatchMessageEvents({ channel, client, newMessage, parentMsg, user: anotherUser });
+      });
+      const counter = screen.queryByTestId(NEW_MESSAGE_COUNTER_TEST_ID);
+      await waitFor(() => {
+        expect(counter).toHaveTextContent(i.toString());
+      });
+    }
+  });
+
+  it(`does not show the count unread of ${containerMsgList} in ${otherMsgList}`, async () => {
+    const [mainListId, threadListId] = ['main-msg-list', 'thread-msg-list'];
+    const [mainListCounterSelector, threadListCounterSelector] = [
+      `#${mainListId} [data-testid="${NEW_MESSAGE_COUNTER_TEST_ID}"]`,
+      `#${threadListId} [data-testid="${NEW_MESSAGE_COUNTER_TEST_ID}"]`,
+    ];
+
+    const messagePayload = containerIsThread
+      ? { parent_id: parentMsg.id, user: anotherUser }
+      : { user: anotherUser };
+    const newMessage = generateMessage(messagePayload);
+
+    const { container } = render(
+      <ChatProvider value={{ channel, client }}>
+        <ChannelStateProvider value={channelStateContext}>
+          <div id={mainListId}>
+            <ScrollToBottomButton isMessageListScrolledToBottom={false} onClick={onClick} />
+          </div>
+          <div id={threadListId}>
+            <ScrollToBottomButton
+              isMessageListScrolledToBottom={false}
+              onClick={onClick}
+              threadList
+            />
+          </div>
+        </ChannelStateProvider>
+      </ChatProvider>,
+    );
+
+    await act(() => {
+      dispatchMessageEvents({ channel, client, newMessage, parentMsg, user: anotherUser });
+    });
+
+    const [containerMsgListCounterSelector, otherMsgListCounterSelector] = containerIsThread
+      ? [threadListCounterSelector, mainListCounterSelector]
+      : [mainListCounterSelector, threadListCounterSelector];
+
+    await waitFor(() => {
+      expect(container.querySelector(containerMsgListCounterSelector)).toBeInTheDocument();
+      expect(container.querySelector(otherMsgListCounterSelector)).not.toBeInTheDocument();
     });
   });
 });
diff --git a/src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.js b/src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.js
index f5bea35d8f..0bb328eefc 100644
--- a/src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.js
+++ b/src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.js
@@ -363,7 +363,7 @@ describe('VirtualizedMessageComponents', () => {
                   class="str-chat__unread-messages-separator"
                   data-testid="unread-messages-separator"
                 >
-                  unreadMessagesSeparatorText
+                  Unread messages
                 </div>
               </div>
             </div>
diff --git a/src/components/MessageList/hooks/MessageList/useUnreadMessagesNotification.ts b/src/components/MessageList/hooks/MessageList/useUnreadMessagesNotification.ts
index 264d7c2315..bbf4749042 100644
--- a/src/components/MessageList/hooks/MessageList/useUnreadMessagesNotification.ts
+++ b/src/components/MessageList/hooks/MessageList/useUnreadMessagesNotification.ts
@@ -3,19 +3,26 @@ import { useEffect, useRef, useState } from 'react';
 import { MESSAGE_LIST_MAIN_PANEL_CLASS } from '../../MessageListMainPanel';
 import { UNREAD_MESSAGE_SEPARATOR_CLASS } from '../../UnreadMessagesSeparator';
 
-const targetIsVisibleInContainer = (element: Element, container: Element) => {
-  const { height: msgListHeight } = container.getBoundingClientRect();
-  const { y: targetMessageY } = element.getBoundingClientRect();
-  return 0 <= targetMessageY && targetMessageY <= msgListHeight;
+const targetScrolledAboveVisibleContainerArea = (element: Element) => {
+  const { bottom: targetBottom } = element.getBoundingClientRect();
+  return targetBottom < 0;
+};
+
+const targetScrolledBelowVisibleContainerArea = (element: Element, container: Element) => {
+  const { top: targetTop } = element.getBoundingClientRect();
+  const { top: containerBottom } = container.getBoundingClientRect();
+  return targetTop > containerBottom;
 };
 
 export type UseUnreadMessagesNotificationParams = {
   isMessageListScrolledToBottom: boolean;
+  showAlways: boolean;
   unreadCount?: number;
 };
 
 export const useUnreadMessagesNotification = ({
   isMessageListScrolledToBottom,
+  showAlways,
   unreadCount,
 }: UseUnreadMessagesNotificationParams) => {
   const { messages } = useChannelStateContext('UnreadMessagesNotification');
@@ -38,19 +45,25 @@ export const useUnreadMessagesNotification = ({
       return;
     }
 
-    setShow(!targetIsVisibleInContainer(observedTarget, msgListPanel));
+    const scrolledBelowSeparator = targetScrolledAboveVisibleContainerArea(observedTarget);
+    const scrolledAboveSeparator = targetScrolledBelowVisibleContainerArea(
+      observedTarget,
+      msgListPanel,
+    );
+
+    setShow(showAlways ? scrolledBelowSeparator || scrolledAboveSeparator : scrolledBelowSeparator);
 
     const observer = new IntersectionObserver(
       (elements) => {
         if (!elements.length) return;
-        const { boundingClientRect, isIntersecting, rootBounds } = elements[0];
-        const isScrolledAboveTargetTopCurrent = !!(
-          rootBounds &&
-          boundingClientRect &&
-          rootBounds.bottom < boundingClientRect.top
-        );
-        setShow(!isIntersecting && !isScrolledAboveTargetTopCurrent);
-        isScrolledAboveTargetTop.current = isScrolledAboveTargetTopCurrent;
+        const { boundingClientRect, isIntersecting } = elements[0];
+        if (isIntersecting) {
+          setShow(false);
+          return;
+        }
+        const separatorIsAboveContainerTop = boundingClientRect.bottom < 0;
+        setShow(showAlways || separatorIsAboveContainerTop);
+        isScrolledAboveTargetTop.current = separatorIsAboveContainerTop;
       },
       { root: msgListPanel },
     );
@@ -59,7 +72,13 @@ export const useUnreadMessagesNotification = ({
     return () => {
       observer.disconnect();
     };
-  }, [intersectionObserverIsSupported, messages, unreadCount]);
+  }, [
+    intersectionObserverIsSupported,
+    isMessageListScrolledToBottom,
+    messages,
+    showAlways,
+    unreadCount,
+  ]);
 
   useEffect(() => {
     /**
diff --git a/src/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.ts b/src/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.ts
index 4c8f7dbf11..f86f9395e6 100644
--- a/src/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.ts
+++ b/src/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.ts
@@ -3,6 +3,7 @@ import { StreamMessage } from '../../../../context';
 import type { DefaultStreamChatGenerics } from '../../../../types/types';
 
 export type UseUnreadMessagesNotificationParams = {
+  showAlways: boolean;
   unreadCount: number;
   lastRead?: Date | null;
 };
@@ -16,25 +17,36 @@ export type UseUnreadMessagesNotificationParams = {
  * `UnreadMessagesNotification` component is rendered. This is an approximate equivalent to being
  * scrolled below the `UnreadMessagesNotification` component.
  * @param lastRead
+ * @param showAlways
  * @param unreadCount
  */
 export const useUnreadMessagesNotificationVirtualized = <
   StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
 >({
   lastRead,
+  showAlways,
   unreadCount,
 }: UseUnreadMessagesNotificationParams) => {
   const [show, setShow] = useState(false);
 
   const toggleShowUnreadMessagesNotification = useCallback(
     (renderedMessages: StreamMessage<StreamChatGenerics>[]) => {
-      if (!renderedMessages.length) return;
-      const firstRenderedMessageTimestamp = renderedMessages[0].created_at
-        ? new Date(renderedMessages[0].created_at).getTime()
-        : 0;
-      setShow(unreadCount > 0 && !!lastRead && firstRenderedMessageTimestamp > lastRead.getTime());
+      if (!unreadCount) return;
+      const firstRenderedMessage = renderedMessages[0];
+      const lastRenderedMessage = renderedMessages.slice(-1)[0];
+      if (!(firstRenderedMessage && lastRenderedMessage)) return;
+      const scrolledBelowSeparator =
+        !!lastRead &&
+        new Date(firstRenderedMessage.created_at as string | Date).getTime() > lastRead.getTime();
+      const scrolledAboveSeparator =
+        !!lastRead &&
+        new Date(lastRenderedMessage.created_at as string | Date).getTime() < lastRead.getTime();
+
+      setShow(
+        showAlways ? scrolledBelowSeparator || scrolledAboveSeparator : scrolledBelowSeparator,
+      );
     },
-    [unreadCount, lastRead],
+    [lastRead, showAlways, unreadCount],
   );
 
   useEffect(() => {
diff --git a/src/components/MessageList/hooks/__tests__/useMarkRead.test.js b/src/components/MessageList/hooks/__tests__/useMarkRead.test.js
index 07727fb5c3..d3e67bb002 100644
--- a/src/components/MessageList/hooks/__tests__/useMarkRead.test.js
+++ b/src/components/MessageList/hooks/__tests__/useMarkRead.test.js
@@ -1,14 +1,29 @@
 import React from 'react';
 import { renderHook } from '@testing-library/react-hooks';
 import { useMarkRead } from '../useMarkRead';
-import { ChannelActionProvider } from '../../../../context';
+import { ChannelActionProvider, ChannelStateProvider, ChatProvider } from '../../../../context';
+import {
+  dispatchMessageNewEvent,
+  generateChannel,
+  generateMessage,
+  generateUser,
+  initClientWithChannels,
+} from '../../../../mock-builders';
+import { act } from 'react-dom/test-utils';
 
 const visibilityChangeScenario = 'visibilitychange event';
 const markRead = jest.fn();
+const setChannelUnreadUiState = jest.fn();
 
-const render = ({ params }) => {
+const render = ({ channel, client, params }) => {
   const wrapper = ({ children }) => (
-    <ChannelActionProvider value={{ markRead }}>{children}</ChannelActionProvider>
+    <ChatProvider value={{ client }}>
+      <ChannelStateProvider value={{ channel }}>
+        <ChannelActionProvider value={{ markRead, setChannelUnreadUiState }}>
+          {children}
+        </ChannelActionProvider>
+      </ChannelStateProvider>
+    </ChatProvider>
   );
   const { result } = renderHook(() => useMarkRead(params), { wrapper });
   return result.current;
@@ -20,13 +35,21 @@ describe('useMarkRead', () => {
     markReadOnScrolledToBottom: true,
     messageListIsThread: false,
     unreadCount: 1,
+    wasMarkedUnread: false,
   };
 
   beforeEach(jest.clearAllMocks);
 
-  describe.each([[visibilityChangeScenario], ['render']])('on %s', (scenario) => {
-    it('should not mark channel read from thread message list', () => {
+  describe.each([[visibilityChangeScenario], ['render'], ['message.new']])('on %s', (scenario) => {
+    it('should not mark channel read from thread message list', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
       render({
+        channel,
+        client,
         params: {
           ...shouldMarkReadParams,
           messageListIsThread: true,
@@ -34,59 +57,450 @@ describe('useMarkRead', () => {
       });
       if (scenario === visibilityChangeScenario) {
         document.dispatchEvent(new Event('visibilitychange'));
+      } else if (scenario === 'message.new') {
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+        expect(setChannelUnreadUiState).not.toHaveBeenCalled();
       }
-      expect(markRead).toHaveBeenCalledTimes(0);
+      expect(markRead).not.toHaveBeenCalled();
     });
 
-    it('should not mark channel read from message list not scrolled to the bottom', () => {
-      render({
+    it('should not mark channel read from message list not scrolled to the bottom', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
         params: {
           ...shouldMarkReadParams,
           isMessageListScrolledToBottom: false,
         },
       });
+
       if (scenario === visibilityChangeScenario) {
         document.dispatchEvent(new Event('visibilitychange'));
+      } else if (scenario === 'message.new') {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+        expect(setChannelUnreadUiState).toHaveBeenCalledTimes(1);
+        const channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.unread_messages).toBe(1);
       }
-      expect(markRead).toHaveBeenCalledTimes(0);
+      expect(markRead).not.toHaveBeenCalled();
     });
 
-    it('should not mark channel read when markReadOnScrolledToBottom is disabled', () => {
-      render({
+    it('should not mark channel read from message list in channel with 0 unread messages', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      const countUnread = jest.spyOn(channel, 'countUnread').mockReturnValueOnce(0);
+
+      await render({
+        channel,
+        client,
         params: {
           ...shouldMarkReadParams,
-          markReadOnScrolledToBottom: false,
+          unreadCount: 0,
         },
       });
+
       if (scenario === visibilityChangeScenario) {
         document.dispatchEvent(new Event('visibilitychange'));
+      } else if (scenario === 'message.new') {
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+        expect(setChannelUnreadUiState).not.toHaveBeenCalled();
       }
-      expect(markRead).toHaveBeenCalledTimes(0);
+
+      expect(markRead).not.toHaveBeenCalled();
+      countUnread.mockRestore();
     });
 
-    it('should not mark channel read from message list in channel with 0 unread messages', () => {
-      render({
+    it('should not mark channel read from non-thread message list scrolled to the bottom previously marked unread', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
         params: {
-          ...shouldMarkReadParams,
-          unreadCount: 0,
+          shouldMarkReadParams,
+          wasMarkedUnread: true,
         },
       });
       if (scenario === visibilityChangeScenario) {
         document.dispatchEvent(new Event('visibilitychange'));
+      } else if (scenario === 'message.new') {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+        expect(setChannelUnreadUiState).toHaveBeenCalledTimes(1);
+        const channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.unread_messages).toBe(1);
       }
-      expect(markRead).toHaveBeenCalledTimes(0);
+
+      expect(markRead).not.toHaveBeenCalled();
     });
 
-    it('should mark channel read from non-thread message list scrolled to the bottom not previously marked unread', () => {
-      render({
+    it('should mark channel read from non-thread message list scrolled to the bottom not previously marked unread', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
         params: shouldMarkReadParams,
       });
       if (scenario === visibilityChangeScenario) {
         document.dispatchEvent(new Event('visibilitychange'));
         expect(markRead).toHaveBeenCalledTimes(2);
+      } else if (scenario === 'message.new') {
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+        expect(markRead).toHaveBeenCalledTimes(2);
+        expect(setChannelUnreadUiState).not.toHaveBeenCalled();
       } else {
         expect(markRead).toHaveBeenCalledTimes(1);
       }
     });
   });
+
+  describe('on message.new', () => {
+    it('should not mark channel read for messages incoming to other channels', async () => {
+      const {
+        channels: [activeChannel, otherChannel],
+        client,
+      } = await initClientWithChannels({ channelsData: [generateChannel(), generateChannel()] });
+
+      await render({
+        channel: activeChannel,
+        client,
+        params: {
+          ...shouldMarkReadParams,
+          unreadCount: 0,
+        },
+      });
+
+      await act(() => {
+        dispatchMessageNewEvent(client, generateMessage(), otherChannel);
+      });
+
+      expect(markRead).not.toHaveBeenCalled();
+      expect(setChannelUnreadUiState).not.toHaveBeenCalled();
+    });
+
+    it('should not mark channel read for own messages', async () => {
+      const user = generateUser();
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels({
+        customUser: user,
+      });
+
+      await render({
+        channel,
+        client,
+        params: {
+          ...shouldMarkReadParams,
+          unreadCount: 0,
+        },
+      });
+
+      await act(() => {
+        dispatchMessageNewEvent(client, generateMessage({ user }), channel);
+      });
+
+      expect(markRead).not.toHaveBeenCalled();
+      expect(setChannelUnreadUiState).not.toHaveBeenCalled();
+    });
+
+    it('should not mark channel read for thread messages', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
+        params: {
+          ...shouldMarkReadParams,
+          unreadCount: 0,
+        },
+      });
+
+      await act(() => {
+        dispatchMessageNewEvent(client, generateMessage({ parent_id: 'X' }), channel);
+      });
+
+      expect(markRead).not.toHaveBeenCalled();
+      expect(setChannelUnreadUiState).not.toHaveBeenCalled();
+    });
+
+    it('should mark channel read for thread messages with event.show_in_channel enabled', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
+        params: {
+          ...shouldMarkReadParams,
+          unreadCount: 0,
+        },
+      });
+
+      await act(() => {
+        dispatchMessageNewEvent(
+          client,
+          generateMessage({ parent_id: 'X', show_in_channel: true }),
+          channel,
+        );
+      });
+
+      expect(markRead).toHaveBeenCalledTimes(1);
+      expect(setChannelUnreadUiState).not.toHaveBeenCalled();
+    });
+
+    it('should mark channel read for not-own messages when scrolled to bottom in main message list', async () => {
+      const {
+        channels: [channel],
+        client,
+      } = await initClientWithChannels();
+
+      await render({
+        channel,
+        client,
+        params: {
+          ...shouldMarkReadParams,
+          unreadCount: 0,
+        },
+      });
+
+      await act(() => {
+        dispatchMessageNewEvent(client, generateMessage(), channel);
+      });
+
+      expect(markRead).toHaveBeenCalledTimes(1);
+      expect(setChannelUnreadUiState).not.toHaveBeenCalled();
+    });
+
+    describe('update unread UI state unread_messages', () => {
+      it('should be performed when message list is not scrolled to bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels();
+
+        await render({
+          channel,
+          client,
+          params: {
+            ...shouldMarkReadParams,
+            isMessageListScrolledToBottom: false,
+          },
+        });
+
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        expect(setChannelUnreadUiState).toHaveBeenCalledTimes(1);
+        const channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.unread_messages).toBe(1);
+      });
+
+      it('should be performed when channel was marked unread and is scrolled to the bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels();
+
+        await render({
+          channel,
+          client,
+          params: {
+            ...shouldMarkReadParams,
+            wasMarkedUnread: true,
+          },
+        });
+
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        expect(setChannelUnreadUiState).toHaveBeenCalledTimes(1);
+        const channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.unread_messages).toBe(1);
+      });
+      it('should be performed when document is hidden and is scrolled to the bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels();
+
+        await render({
+          channel,
+          client,
+          params: shouldMarkReadParams,
+        });
+
+        const docHiddenSpy = jest.spyOn(document, 'hidden', 'get').mockReturnValueOnce(true);
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        expect(setChannelUnreadUiState).toHaveBeenCalledTimes(1);
+        const channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.unread_messages).toBe(1);
+        docHiddenSpy.mockRestore();
+      });
+    });
+
+    describe('update unread UI state last_read', () => {
+      it('should be performed when message list is not scrolled to bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementationOnce((cb) => (channelUnreadUiStateCb = cb));
+        const channelsData = [
+          generateChannel({ messages: Array.from({ length: 2 }, generateMessage) }),
+        ];
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels({
+          channelsData,
+        });
+
+        await render({
+          channel,
+          client,
+          params: {
+            ...shouldMarkReadParams,
+            isMessageListScrolledToBottom: false,
+          },
+        });
+
+        await act(() => {
+          dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        const prevLastRead = 'X';
+        let channelUnreadUiState = channelUnreadUiStateCb({ last_read: prevLastRead });
+        expect(channelUnreadUiState.last_read).toBe(prevLastRead);
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 0 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[1].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[1].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 1 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(0);
+      });
+
+      it('should be performed when channel was marked unread and is scrolled to the bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementation((cb) => (channelUnreadUiStateCb = cb));
+        const channelsData = [generateChannel({ messages: [generateMessage()] })];
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels({
+          channelsData,
+        });
+
+        await render({
+          channel,
+          client,
+          params: {
+            ...shouldMarkReadParams,
+            wasMarkedUnread: true,
+          },
+        });
+
+        await act(async () => {
+          await dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        const prevLastRead = 'X';
+        let channelUnreadUiState = channelUnreadUiStateCb({ last_read: prevLastRead });
+        expect(channelUnreadUiState.last_read).toBe(prevLastRead);
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 0 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[0].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[0].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 1 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(0);
+      });
+
+      it('should be performed when document is hidden and is scrolled to the bottom', async () => {
+        let channelUnreadUiStateCb;
+        setChannelUnreadUiState.mockImplementation((cb) => (channelUnreadUiStateCb = cb));
+        const channelsData = [generateChannel({ messages: [generateMessage()] })];
+        const {
+          channels: [channel],
+          client,
+        } = await initClientWithChannels({
+          channelsData,
+        });
+
+        await render({
+          channel,
+          client,
+          params: shouldMarkReadParams,
+        });
+
+        const docHiddenSpy = jest.spyOn(document, 'hidden', 'get').mockReturnValueOnce(true);
+        await act(async () => {
+          await dispatchMessageNewEvent(client, generateMessage(), channel);
+        });
+
+        const prevLastRead = 'X';
+        let channelUnreadUiState = channelUnreadUiStateCb({ last_read: prevLastRead });
+        expect(channelUnreadUiState.last_read).toBe(prevLastRead);
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 0 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[0].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb();
+        expect(channelUnreadUiState.last_read.getTime()).toBe(
+          channelsData[0].messages[0].created_at.getTime(),
+        );
+        channelUnreadUiState = channelUnreadUiStateCb({ unread_messages: 1 });
+        expect(channelUnreadUiState.last_read.getTime()).toBe(0);
+        docHiddenSpy.mockRestore();
+      });
+    });
+  });
 });
diff --git a/src/components/MessageList/hooks/__tests__/useUnreadMessagesNotificationVirtualized.test.js b/src/components/MessageList/hooks/__tests__/useUnreadMessagesNotificationVirtualized.test.js
index 4adcc9043e..431996bf70 100644
--- a/src/components/MessageList/hooks/__tests__/useUnreadMessagesNotificationVirtualized.test.js
+++ b/src/components/MessageList/hooks/__tests__/useUnreadMessagesNotificationVirtualized.test.js
@@ -21,7 +21,7 @@ describe('useUnreadMessagesNotificationVirtualized', () => {
   });
 
   describe('toggle function', () => {
-    it('should prevent show state change when there no messages to render', async () => {
+    it('should prevent show state change when there are no messages to render', async () => {
       const { rerender, result } = render({ unreadCount: 0 });
       await act(() => {
         result.current.toggleShowUnreadMessagesNotification([]);
@@ -29,20 +29,6 @@ describe('useUnreadMessagesNotificationVirtualized', () => {
       rerender({ lastRead: new Date('1970-1-1'), unreadCount: 1 });
       expect(result.current.show).toBe(false);
     });
-    it('should show notification if there are unread messages and first rendered message was created later than last read', async () => {
-      const now = new Date();
-      const lastRead = new Date(now - 1000);
-      const firstRenderedMsgCreated = new Date(now - 500);
-      const messages = [
-        generateMessage({ created_at: firstRenderedMsgCreated }),
-        generateMessage({ created_at: now }),
-      ];
-      const { result } = render({ lastRead, unreadCount: 1 });
-      await act(() => {
-        result.current.toggleShowUnreadMessagesNotification(messages);
-      });
-      expect(result.current.show).toBe(true);
-    });
 
     it('should not show notification if unread count is 0', async () => {
       const now = new Date();
@@ -52,40 +38,103 @@ describe('useUnreadMessagesNotificationVirtualized', () => {
         generateMessage({ created_at: firstRenderedMsgCreated }),
         generateMessage({ created_at: now }),
       ];
-      const { result } = render({ lastRead, unreadCount: 0 });
+      const { result } = render({ lastRead, showAlways: false, unreadCount: 0 });
       await act(() => {
         result.current.toggleShowUnreadMessagesNotification(messages);
       });
       expect(result.current.show).toBe(false);
     });
 
-    it('should not show notification if the first rendered message was created earlier than last read', async () => {
-      const now = new Date();
-      const lastRead = new Date(now - 1000);
-      const firstRenderedMsgCreated = new Date(now - 1001);
-      const messages = [
-        generateMessage({ created_at: firstRenderedMsgCreated }),
-        generateMessage({ created_at: now }),
-      ];
-      const { result } = render({ lastRead, unreadCount: 1 });
-      await act(() => {
-        result.current.toggleShowUnreadMessagesNotification(messages);
-      });
-      expect(result.current.show).toBe(false);
-    });
+    it.each([[true], [false]])(
+      'should show notification if there are unread messages and first rendered message was created later than last read when showUnreadNotificationAlways is %s',
+      async (showUnreadNotificationAlways) => {
+        const now = new Date();
+        const lastRead = new Date(now - 1000);
+        const firstRenderedMsgCreated = new Date(now - 500);
+        const messages = [
+          generateMessage({ created_at: firstRenderedMsgCreated }),
+          generateMessage({ created_at: now }),
+        ];
+        const { result } = render({
+          lastRead,
+          showAlways: showUnreadNotificationAlways,
+          unreadCount: 1,
+        });
+        await act(() => {
+          result.current.toggleShowUnreadMessagesNotification(messages);
+        });
+        expect(result.current.show).toBe(true);
+      },
+    );
 
-    it('should not show notification if the first rendered message was created equal to last read', async () => {
-      const now = new Date();
-      const lastRead = new Date(now - 1000);
-      const messages = [
-        generateMessage({ created_at: lastRead }),
-        generateMessage({ created_at: now }),
-      ];
-      const { result } = render({ lastRead, unreadCount: 1 });
-      await act(() => {
-        result.current.toggleShowUnreadMessagesNotification(messages);
-      });
-      expect(result.current.show).toBe(false);
-    });
+    it.each([
+      ['should', true],
+      ['should not', false],
+    ])(
+      '%s show notification if the last rendered message was created earlier than last read when showUnreadNotificationAlways is %s',
+      async (_, showUnreadNotificationAlways) => {
+        const now = new Date();
+        const firstRenderedMsgCreated = new Date(now - 1002);
+        const lastRenderedMsgCreated = new Date(now - 1001);
+        const lastRead = new Date(now - 1000);
+        const messages = [
+          generateMessage({ created_at: firstRenderedMsgCreated }),
+          generateMessage({ created_at: lastRenderedMsgCreated }),
+        ];
+        const { result } = render({
+          lastRead,
+          showAlways: showUnreadNotificationAlways,
+          unreadCount: 1,
+        });
+        await act(() => {
+          result.current.toggleShowUnreadMessagesNotification(messages);
+        });
+        expect(result.current.show).toBe(showUnreadNotificationAlways);
+      },
+    );
+
+    it.each([[true], [false]])(
+      'should not show notification if the first rendered message was created earlier than last read when showUnreadNotificationAlways is %s',
+      async (showUnreadNotificationAlways) => {
+        const now = new Date();
+        const firstRenderedMsgCreated = new Date(now - 1002);
+        const lastRead = new Date(now - 1001);
+        const messages = [
+          generateMessage({ created_at: firstRenderedMsgCreated }),
+          generateMessage({ created_at: lastRead }),
+        ];
+        const { result } = render({
+          lastRead,
+          showAlways: showUnreadNotificationAlways,
+          unreadCount: 1,
+        });
+        await act(() => {
+          result.current.toggleShowUnreadMessagesNotification(messages);
+        });
+        expect(result.current.show).toBe(false);
+      },
+    );
+
+    it.each([[true], [false]])(
+      'should not show notification if the last rendered message was created earlier than last read when showUnreadNotificationAlways is %s',
+      async (showUnreadNotificationAlways) => {
+        const now = new Date();
+        const lastRead = new Date(now - 1001);
+        const lastRenderedMsgCreated = new Date(now - 1000);
+        const messages = [
+          generateMessage({ created_at: lastRead }),
+          generateMessage({ created_at: lastRenderedMsgCreated }),
+        ];
+        const { result } = render({
+          lastRead,
+          showAlways: showUnreadNotificationAlways,
+          unreadCount: 1,
+        });
+        await act(() => {
+          result.current.toggleShowUnreadMessagesNotification(messages);
+        });
+        expect(result.current.show).toBe(false);
+      },
+    );
   });
 });
diff --git a/src/components/MessageList/hooks/useMarkRead.ts b/src/components/MessageList/hooks/useMarkRead.ts
index 0a17ac9149..04a9b1e8a4 100644
--- a/src/components/MessageList/hooks/useMarkRead.ts
+++ b/src/components/MessageList/hooks/useMarkRead.ts
@@ -1,11 +1,18 @@
-import { useEffect } from 'react';
-import { useChannelActionContext } from '../../../context';
+import { useEffect, useRef } from 'react';
+import {
+  StreamMessage,
+  useChannelActionContext,
+  useChannelStateContext,
+  useChatContext,
+} from '../../../context';
+import { Event, MessageResponse } from 'stream-chat';
+import { DefaultStreamChatGenerics } from '../../../types';
 
 type UseMarkReadParams = {
   isMessageListScrolledToBottom: boolean;
   messageListIsThread: boolean;
   unreadCount: number;
-  markReadOnScrolledToBottom?: boolean;
+  wasMarkedUnread?: boolean;
 };
 
 /**
@@ -18,37 +25,100 @@ type UseMarkReadParams = {
  * @param unreadCount
  * @param wasChannelMarkedUnread
  */
-export const useMarkRead = ({
+export const useMarkRead = <
+  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
+>({
   isMessageListScrolledToBottom,
-  markReadOnScrolledToBottom,
   messageListIsThread,
   unreadCount,
+  wasMarkedUnread,
 }: UseMarkReadParams) => {
-  const { markRead } = useChannelActionContext('useMarkRead');
+  const { client } = useChatContext<StreamChatGenerics>('useMarkRead');
+  const { markRead, setChannelUnreadUiState } = useChannelActionContext('useMarkRead');
+  const { channel } = useChannelStateContext('useMarkRead');
+  const previousRenderMessageListScrolledToBottom = useRef(isMessageListScrolledToBottom);
 
   useEffect(() => {
-    const shouldMarkRead =
+    const shouldMarkRead = (unreadMessages: number) =>
+      !document.hidden &&
+      !wasMarkedUnread &&
       !messageListIsThread &&
       isMessageListScrolledToBottom &&
-      markReadOnScrolledToBottom &&
-      unreadCount > 0;
+      unreadMessages > 0;
 
     const onVisibilityChange = () => {
-      if (!document.hidden && shouldMarkRead) markRead();
+      if (shouldMarkRead(unreadCount)) markRead();
     };
 
+    const handleMessageNew = (event: Event<StreamChatGenerics>) => {
+      const newMessageToCurrentChannel = event.cid === channel.cid;
+      const isOwnMessage = event.user?.id && event.user.id === client.user?.id;
+      const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
+      if (isOwnMessage) return;
+      if (!isMessageListScrolledToBottom || wasMarkedUnread || document.hidden) {
+        setChannelUnreadUiState((prev) => {
+          const previousUnreadCount = prev?.unread_messages ?? 0;
+          const previousLastMessage = getPreviousLastMessage<StreamChatGenerics>(
+            channel.state.messages,
+            event.message,
+          );
+          return {
+            ...(prev || {}),
+            last_read:
+              prev?.last_read ??
+              (previousUnreadCount === 0 && previousLastMessage?.created_at
+                ? new Date(previousLastMessage.created_at)
+                : new Date(0)), // not having information about the last read message means the whole channel is unread,
+            unread_messages: previousUnreadCount + 1,
+          };
+        });
+      } else if (
+        newMessageToCurrentChannel &&
+        mainChannelUpdated &&
+        !isOwnMessage &&
+        shouldMarkRead(channel.countUnread())
+      ) {
+        markRead();
+      }
+    };
+
+    client.on('message.new', handleMessageNew);
     document.addEventListener('visibilitychange', onVisibilityChange);
 
-    if (shouldMarkRead) markRead();
+    const hasScrolledToBottom =
+      previousRenderMessageListScrolledToBottom.current !== isMessageListScrolledToBottom &&
+      isMessageListScrolledToBottom;
+    if (shouldMarkRead(hasScrolledToBottom ? channel.countUnread() : unreadCount)) markRead();
+    previousRenderMessageListScrolledToBottom.current = isMessageListScrolledToBottom;
 
     return () => {
+      client.off('message.new', handleMessageNew);
       document.removeEventListener('visibilitychange', onVisibilityChange);
     };
   }, [
+    channel,
+    client,
     isMessageListScrolledToBottom,
     markRead,
     messageListIsThread,
+    setChannelUnreadUiState,
     unreadCount,
-    markReadOnScrolledToBottom,
+    wasMarkedUnread,
   ]);
 };
+
+function getPreviousLastMessage<
+  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
+>(messages: StreamMessage<StreamChatGenerics>[], newMessage?: MessageResponse<StreamChatGenerics>) {
+  if (!newMessage) return;
+  let previousLastMessage;
+  for (let i = messages.length - 1; i >= 0; i--) {
+    const msg = messages[i];
+    if (!msg?.id) break;
+    if (msg.id !== newMessage.id) {
+      previousLastMessage = msg;
+      break;
+    }
+  }
+  return previousLastMessage;
+}
diff --git a/src/context/ChannelActionContext.tsx b/src/context/ChannelActionContext.tsx
index afd2bd82c9..0ee09711e6 100644
--- a/src/context/ChannelActionContext.tsx
+++ b/src/context/ChannelActionContext.tsx
@@ -17,6 +17,7 @@ import type { ChannelStateReducerAction } from '../components/Channel/channelSta
 import type { CustomMentionHandler } from '../components/Message/hooks/useMentionsHandler';
 
 import type {
+  ChannelUnreadUiState,
   DefaultStreamChatGenerics,
   SendMessageOptions,
   UnknownType,
@@ -87,6 +88,7 @@ export type ChannelActionContextValue<
     customMessageData?: Partial<Message<StreamChatGenerics>>,
     options?: SendMessageOptions,
   ) => Promise<void>;
+  setChannelUnreadUiState: React.Dispatch<React.SetStateAction<ChannelUnreadUiState | undefined>>;
   setQuotedMessage: React.Dispatch<
     React.SetStateAction<StreamMessage<StreamChatGenerics> | undefined>
   >;