Message Delivery & Read status

Sent messages go through multiple states reflecting the receiving side’s interaction with the messages:

  • sent - The message has reached the Stream server successfully after it was created from the client device. The WebSocket message.new event is sent to the client to confirm the message creation.
  • delivered - Requires the client device to confirm the delivery (e.g. upon receiving message.new event or querying the channels directly). The message delivery is confirmed to the message author and other channel members via the message.delivered WS event. This information is disabled by default, but can be enabled through the channel type dashboard.
  • read - Requires the client to confirm the channel has been read when opening the channel. This is confirmed to the channel members via message.read WS event. It is not possible to mark a specific message in a channel as read, only the whole channel.

Enabling Delivery Receipts Tracking

Please contact our support team to enable message delivery tracking in your app.

It is possible to control the message delivery tracking on two levels:

Channel type (for all members)

Message delivery tracking can be enabled in the channel type configuration.

// when creating a channel
await client.createChannelType("targetChannelType", { delivery_events: true });
// updating an existing channel
await client.updateChannelType("targetChannelType", { delivery_events: true });

The feature can be enabled in dashboard as well (channel type configuration).

User

On the user level the configuration can be done programmatically only. We can control the delivery confirmation reporting for a given user as follows:

await client.upsertUser({
  id: "user-id",
  privacy_settings: {
    delivery_receipts: {
      enabled: false, // do not report even though the channel read events are enabled
    },
  },
});

If privacy_settings.delivery_receipts.enabled is set to false, then the message delivery state of this user will not be exposed to others. Additionally, the related event message.delivered, will not be delivered to others when this user confirms the delivery messages.

Enabling Read Receipts Tracking

It is possible to control the message read status tracking on two levels:

Channel type (for all members)

Message read status tracking can be enabled in a channel type configuration.

// when creating a channel
await client.createChannelType("targetChannelType", { read_events: true });
// updating an existing channel
await client.updateChannelType("targetChannelType", { read_events: true });

The feature can be enabled in dashboard as well (channel type configuration).

User

On the user level the configuration can be done programmatically only.

await client.upsertUser({
  id: "user-id",
  privacy_settings: {
    read_receipts: {
      enabled: false, // do not report even though the channel read events are enabled
    },
  },
});

If privacy_settings.read_receipts.enabled is set to false, then the read state of this user will not be exposed to others. Additionally, the related events, such as message.read and notification.mark_read, will not be delivered to others when this user reads messages.

Marking Channel Messages as Delivered

Message delivery tracking is currently supported only for the channel message list, not thread replies.

The SDK will automatically handle the process of reporting message delivery from the user receiving the message to the server and from there to the other users. The SDK takes care of request throttling, duplicate request prevention and synchronization rules between the message receipt states.

Marking a Channel as Read

We can mark all messages in a channel as read client-side as follows:

channelClient.markRead().enqueue(result -> {
  if (result.isSuccess()) {
    // Messages in the channel marked as read
  } else {
    // Handle result.error()
  }
});

Or from server-side by providing a user id:

await channel.markRead({ user_id: "foo" });

Marking a Message as Unread

Users can have unread messages in a channel because new messages arrived while they were away, or because they explicitly marked a message as unread. Marking a message unread sets a new last read message reference. The message delivery reference does not change. You can mark a message as unread from client-side:

val channelClient = chatClient.channel(cid = "<channel_cid>")
channelClient.markUnread(messageId = "<message_id>").enqueue { result ->
    if (result is Result.Success) {
        // Message marked as unread
    } else {
        // Handle Result.Failure
    }
}

Or from server-side by providing a user id:

await channel.markUnread({ message_id: "<message_id>", user_id: "<user_id>" });

For performance reasons it’s only possible to mark any of the last 100 messages of the channel as unread.

Read state

When you retrieve a channel from the API (e.g. using query channels), the read state for members is included in the response (up to 100 members, ordered by the most recent added, the current user’s read state is always included).

The read state includes the following fields:

  • last_read - the timestamp when the channel has been marked read the last time by the given user
  • user - user whose read state is being described

And optionally includes:

  • last_read_message_id- the last message reported as read in the channel by the user
  • unread_messages - the number of unread messages for a given user in the channel
  • last_delivered_at - the timestamp when the channel messages have been reported as delivered the last time by the given user
  • last_delivered_message_id - the last message reported as delivered in the channel by the user

The members read states can be retrieved by query the channel data:

val channelClient = chatClient.channel(cid = "<channel_cid>")
channelClient.get(
    messageLimit = 10,
    memberLimit = 10,
    state = true
).enqueue { result ->
    if (result is Result.Success) {
        val channel = result.value
        val channelRead = channel.read.find { it.user.id == "<user_id>" }
        if (channelRead != null) {
            // When the user read the channel
            println(channelRead.lastRead)
            // ID of the last read message
            println(channelRead.lastReadMessageId)
            // Number of unread messages for the user in the channel
            println(channelRead.unreadMessages)
        }
    } else {
        // Handle Result.Failure
    }
}

The last read message id is updated in the following events:

  • message.read

  • notification.mark_read

  • notification.mark_unread

val channelClient = chatClient.channel(cid = "<channel_cid>")
channelClient.watch().enqueue()

// Any member read the channel
channelClient.subscribeFor<MessageReadEvent> { event ->
    println(event.lastReadMessageId)
    // Unread messages: 0
}

// Connected user read the channel
channelClient.subscribeFor<NotificationMarkReadEvent> { event ->
    println(event.lastReadMessageId)
    // Unread messages: 0
}

// Connected user marked a message as unread
channelClient.subscribeFor<NotificationMarkUnreadEvent> { event ->
    println(event.lastReadMessageId)
    // Unread messages
    println(event.unreadMessages)
}

The last delivered message id is updated in the following events:

  • message.delivered

Jump to last read message

This is how you can jump to the last read message inside a given channel:

const channel = client.channel("messaging", "test");
await channel.watch();

const lastReadMessageId = channel.state.read["<user id>"];
await channel.state.loadMessageIntoState(lastReadMessageId);

console.log(channel.state.messages);

Message Delivery Push Notifications

By default, when a user receives a push notification for a new message, and the app is not active, the message is not marked as delivered. You can change this behavior by calling the mark delivered endpoint when a push notification is received for a new message. For this, you will need to customize the push notifications depending on the platform.

// By using the `ChatRemoteNotificationHandler` in the NotificationService extension
// you can use the provided `markMessageDelivered` method to mark the message as delivered.
let chatHandler = ChatRemoteNotificationHandler(client: client, content: content)
let chatNotification = chatHandler.handleNotification { chatContent in
    switch chatContent {
    case let .message(messageNotification):
        switch messageNotification.type {
        case .messageNew:
            // Mark the message as delivered
            if let channel = messageNotification.channel {
                chatHandler.markMessageAsDelivered(messageNotification.message, for: channel)
            }
        default:
            break
        }
    }
}
© Getstream.io, Inc. All Rights Reserved.