# Message Status Indicators

The SDK provides built-in message status indicators so users can see message delivery states.

## Best Practices

- Enable read and delivery events per channel type before relying on indicators.
- Use `MessageStatus` overrides to hide indicators in low-signal contexts.
- Distinguish delivered vs. read clearly to avoid user confusion.
- Avoid showing your own user in read/delivered lists.
- Keep status UI lightweight to prevent re-render churn on busy channels.

## Message status indicators

This cookbook shows how to use and customize those indicators.

### Status indicators states

Possible states:

- **sending:** The message is pending to be sent to the server. It shows a timer icon.
- **received:** The message has been received by the server successfully. It is shown as a gray checkmark.
- **delivered:** The message has been delivered to at least one of the channel members devices. It is shown as double gray checkmark.
- **read:** The message has been read by at least one of the channel members. It is shown as double blue checkmark.

<admonition type="info">

The **delivered** state is only available since version [8.8.0](https://github.com/GetStream/stream-chat-react-native/v8/releases/tag/v8.8.0) and it needs to be enabled in the Dashboard for each channel type by enabling the `Delivery Events` flag/switch.

The `read_events` flag needs to be enabled in the Dashboard for each channel type by enabling the `Read Events` flag/switch.

</admonition>

| Pending                                                                                                               | Sent                                                                                                            | Delivered                                                                                                                 | Read                                                                                                            | Read by many                                                                                                                |
| --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| ![Message Status Pending](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/status-pending.png) | ![Message Status Sent](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/status-sent.png) | ![Message Status Delivered](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/status-delivered.png) | ![Message Status Read](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/status-read.png) | ![Message Status Read Group](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/status-read-group.png) |

## Basic Customization

### Hide status indicators

To hide indicators, override `MessageStatus` on `Channel` and return `null`.

```tsx
<Channel MessageStatus={() => null}>
  <MessageList />
</Channel>
```

### Show all read and delivered members

![Message Delivery State Read and Delivered](@chat-sdk/react-native/v8/_assets/ui-cookbook/message-status-indicators/delivered-and-read-sheet.png)

Example: a custom bottom sheet showing who has read vs. only received.

1. First, create the bottom sheet component that will be used to show the members that have read the message and the ones that were delivered but not read yet.

```tsx
import React, { useMemo } from "react";
import BottomSheet, { BottomSheetFlatList } from "@gorhom/bottom-sheet";
import { BottomSheetView } from "@gorhom/bottom-sheet";
import {
  Avatar,
  useChatContext,
  useMessageDeliveredData,
  useMessageReadData,
  useTheme,
} from "stream-chat-react-native";
import { LocalMessage, UserResponse } from "stream-chat";
import { StyleSheet, Text, View } from "react-native";

const renderUserItem = ({ item }: { item: UserResponse }) => (
  <View style={styles.userItem}>
    <Avatar image={item.image} name={item.name ?? item.id} size={32} />
    <Text style={styles.userName}>{item.name ?? item.id}</Text>
  </View>
);

const renderEmptyText = ({ text }: { text: string }) => (
  <Text style={styles.emptyText}>{text}</Text>
);

export const MessageInfoBottomSheet = ({
  message,
  ref,
}: {
  message?: LocalMessage;
  ref: React.RefObject<BottomSheet | null>;
}) => {
  const {
    theme: { colors },
  } = useTheme();
  const { client } = useChatContext();
  const deliveredStatus = useMessageDeliveredData({ message });
  const readStatus = useMessageReadData({ message });

  const otherDeliveredToUsers = useMemo(() => {
    return deliveredStatus.filter(
      (user: UserResponse) => user.id !== client?.user?.id,
    );
  }, [deliveredStatus, client?.user?.id]);

  const otherReadUsers = useMemo(() => {
    return readStatus.filter(
      (user: UserResponse) => user.id !== client?.user?.id,
    );
  }, [readStatus, client?.user?.id]);

  return (
    <BottomSheet enablePanDownToClose ref={ref} index={-1} snapPoints={["50%"]}>
      <BottomSheetView
        style={[styles.container, { backgroundColor: colors.white_smoke }]}
      >
        <Text style={styles.title}>Read</Text>
        <BottomSheetFlatList
          data={otherReadUsers}
          renderItem={renderUserItem}
          keyExtractor={(item) => item.id}
          style={styles.flatList}
          ListEmptyComponent={renderEmptyText({
            text: "No one has read this message.",
          })}
        />
        <Text style={styles.title}>Delivered</Text>
        <BottomSheetFlatList
          data={otherDeliveredToUsers}
          renderItem={renderUserItem}
          keyExtractor={(item) => item.id}
          style={styles.flatList}
          ListEmptyComponent={renderEmptyText({
            text: "The message was not delivered to anyone.",
          })}
        />
      </BottomSheetView>
    </BottomSheet>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    justifyContent: "center",
    height: "100%",
  },
  title: {
    fontSize: 16,
    fontWeight: "bold",
    marginVertical: 8,
  },
  flatList: {
    borderRadius: 16,
  },
  userItem: {
    flexDirection: "row",
    alignItems: "center",
    padding: 8,
    backgroundColor: "white",
  },
  userName: {
    fontSize: 16,
    fontWeight: "bold",
    marginLeft: 16,
  },
  emptyText: {
    fontSize: 16,
    marginVertical: 16,
    textAlign: "center",
  },
});
```

2. Open this bottom sheet via a custom message action (see [Custom Message Actions](/chat/docs/sdk/react-native/v8/guides/customize-message-actions/)).

## Mark as Delivered on Background push notification on Android

This example shows how to mark a message as delivered on Android background push notifications.

Use `setBackgroundMessageHandler` to mark messages as delivered in the background.

Example:

```tsx title="bootstrapBackgroundMessageHandler.ts"
import {
  FirebaseMessagingTypes,
  setBackgroundMessageHandler,
} from '@react-native-firebase/messaging';

const displayNotification = async (
  remoteMessage: FirebaseMessagingTypes.RemoteMessage,
  channelId: string,
) => {
  const { stream, ...rest } = remoteMessage.data ?? {};
  const data = {
    ...rest,
    ...((stream as unknown as Record<string, string> | undefined) ?? {}), // extract and merge stream object if present
  };
  if (data.body && data.title) {
    await notifee.displayNotification({
      android: {
        channelId,
        pressAction: {
          id: 'default',
        },
      },
      body: data.body as string,
      title: data.title as string,
      data,
    });
  }
};

setBackgroundMessageHandler(messaging, async (remoteMessage) => {
  try {
    const loginConfig = // get the login config from the storage
    if (!loginConfig) {
      return;
    }
    const chatClient = StreamChat.getInstance(loginConfig.apiKey);
    await chatClient._setToken(
      { id: loginConfig.userId },
      loginConfig.userToken,
    );

    const notification = remoteMessage.data;

    const deliverMessageConfirmation = [
      {
        cid: notification?.cid,
        id: notification?.id,
      },
    ];

    await chatClient?.markChannelsDelivered({
      latest_delivered_messages:
        deliverMessageConfirmation as DeliveredMessageConfirmation[],
    });
    // create the android channel to send the notification to
    const channelId = await notifee.createChannel({
      id: "chat-messages",
      name: "Chat Messages",
    });
    // display the notification
    await displayNotification(remoteMessage, channelId);
  } catch (error) {
    console.error(error);
  }
});
```

Now, import the `bootstrapBackgroundMessageHandler.ts` file in the `index.ts` on the top of the file before the `App` component.

```tsx title="index.ts"
import "./bootstrapBackgroundMessageHandler";
```

Now, you can test the above by sending a push notification to the app.


---

This page was last updated at 2026-04-17T17:33:45.269Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/guides/message-status-indicators/](https://getstream.io/chat/docs/sdk/react-native/v8/guides/message-status-indicators/).