// when creating a channel
await client.createChannelType("targetChannelType", { delivery_events: true });
// updating an existing channel
await client.updateChannelType("targetChannelType", { delivery_events: true });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.newevent is sent to the client to confirm the message creation. - delivered - Requires the client device to confirm the delivery (e.g. upon receiving
message.newevent or querying the channels directly). The message delivery is confirmed to the message author and other channel members via themessage.deliveredWS 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.readWS 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.
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
},
},
});let currentUserController = ChatClient.shared.currentUserController()
currentUserController.updateUserData(
privacySettings: .init(
deliveryReceipts: DeliveryReceiptsPrivacySettings(enabled: false)
)
)val chatClient = ChatClient.instance()
chatClient.getCurrentUser()?.let { currentUser ->
chatClient.updateUser(
user = currentUser.copy(
privacySettings = currentUser.privacySettings?.copy(
deliveryReceipts = DeliveryReceipts(enabled = false),
)
)
)
}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
},
},
});let currentUserController = ChatClient.shared.currentUserController()
currentUserController.updateUserData(
privacySettings: .init(
readReceipts: ReadReceiptsPrivacySettings(enabled: false)
)
)val chatClient = ChatClient.instance()
chatClient.getCurrentUser()?.let { currentUser ->
chatClient.updateUser(
user = currentUser.copy(
privacySettings = currentUser.privacySettings?.copy(
readReceipts = ReadReceipts(enabled = false),
)
)
)
}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:
val channelClient = chatClient.channel(cid = "<channel_cid>")
channelClient.markRead().enqueue { result ->
if (result is Result.Success) {
// Channel marked as read
} else {
// Handle Result.Failure
}
}await channel.markRead();await channel.markRead();let channelId = ChannelId(type: .messaging, id: "general")
let channelController = ChatClient.shared.channelController(for: channelId)
channelController.markRead()Channel->MarkRead();channelClient.markRead().enqueue(result -> {
if (result.isSuccess()) {
// Messages in the channel marked as read
} else {
// Handle result.error()
}
});await message.MarkMessageAsLastReadAsync();Or from server-side by providing a user id:
await channel.markRead({ user_id: "foo" });$mark = $channel->markRead('user-id');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
}
}await channel.markUnread({ message_id: "<message_id>" });let channelId = ChannelId(type: .messaging, id: "general")
let channelController = ChatClient.shared.channelController(for: channelId)
channelController.markUnread(from: "message-id")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 useruser- 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 userunread_messages- the number of unread messages for a given user in the channellast_delivered_at- the timestamp when the channel messages have been reported as delivered the last time by the given userlast_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
}
}const channel = client.channel("messaging", "test");
await channel.watch();
console.log(channel.state.read["<user id>"]);
{
last_read: "2024-02-09T12:23:46.361362996Z";
last_read_message_id: "sara-e9c0d141-b109-4116-baec-e76966ae712e";
unread_messages: 3;
}let channelId = ChannelId(type: .messaging, id: "general")
let channelController = ChatClient.shared.channelController(for: channelId)
channelListController.synchronize { error in
if let error = error {
// Handle error
return
}
let read: ChatChannelRead? = channelController.channel?.read(for: "<user id>")
print(read)
}The last read message id is updated in the following events:
message.readnotification.mark_readnotification.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)
}const channel = client.channel("messaging", "test");
await channel.watch();
// Any member read the channel
channel.on("message.read", (event) => {
console.log(event.last_read_message_id);
// Unread messages: 0
});
// Connected user read the channel
channel.on("notification.message_read", (event) => {
console.log(event.last_read_message_id);
// Unread messages: 0
});
// Connected user marked a message as unread
channel.on("notification.message_unread", (event) => {
console.log(event.last_read_message_id);
// Unread messages
console.log(event.unread_messages);
});let channelId = ChannelId(type: .messaging, id: "general")
let channelEventsController = ChatClient.shared.channelController(for: channelId)
channelEventsController.delegate = self
// Handle the delegate event
extension CustomViewController: EventsControllerDelegate {
func eventsController(_ controller: EventsController, didReceiveEvent event: Event) {
if let event = event as? MessageReadEvent {
print(event.lastReadMessageId)
} else if let event = event as? NotificationMarkReadEvent {
print(event.lastReadMessageId)
} else if let event = event as? NotificationMarkUnreadEvent {
print(event.lastReadMessageId)
print(event.unreadCount?.messages)
}
}
}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);let channelId = ChannelId(type: .messaging, id: "general")
let channelController = ChatClient.shared.channelController(for: channelId)
channelController.synchronize { error in
if let error = error {
// Handle error
return
}
// The last read message id for the current user
if let lastReadMessageId = channelController.lastReadMessageId {
channelController.loadPageAroundMessageId(lastReadMessageId)
}
}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
}
}
}// You can implement `onNewMessage` while creating the notification handler
// to mark the message as delivered when a new message notification is received.
val notificationHandler = NotificationHandlerFactory.createNotificationHandler(
context = context,
notificationConfig = notificationConfig,
onNewMessage = { pushMessage ->
ChatClient.instance()
.markMessageAsDelivered(messageId = pushMessage.messageId)
.enqueue()
// Return false to let the SDK handle the push message and show a notification
false
},
)
ChatClient.Builder(apiKey, context)
.notifications(notificationConfig, notificationHandler)
// If you have your own custom notification handler, you can override the `onPushMessage` method
// to mark the message as delivered when a new message notification is received.
class MyCustomNotificationHandler : NotificationHandler {
override fun onPushMessage(message: PushMessage): Boolean {
ChatClient.instance()
.markMessageAsDelivered(messageId = message.messageId)
.enqueue()
// Return false to let the SDK handle the push message and show a notification
return false
}
}