Support read receipts in your app built with XMTP
Use the read receipt content type to support read receipts in your app. A read receipt is a timestamp
that indicates when a message was read. It is sent as a message and can be used to calculate the time since the last message was read.
This standards-track content type is in Alpha status as this implementation doesn't work efficiently with the current protocol architecture. This inefficiency will be addressed in a future protocol release.
Until then, if you must support read receipts, we recommend that you use this implementation and not build your own custom content type.
You're welcome to provide feedback by commenting on the Proposal for read receipts content type XIP idea.
Provide an opt-out option
While this is a per-app decision, the best practice is to provide users with the option to opt out of sending read receipts. If a user opts out, when they read a message, a read receipt will not be sent to the sender of the message.
Configure the content type
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
npm i @xmtp/content-type-read-receipt
Import and register
import {
ContentTypeReadReceipt,
ReadReceiptCodec,
} from "@xmtp/content-type-read-receipt";
// Create the XMTP client
const xmtp = await Client.create(signer, { env: "dev" });
xmtp.registerCodec(new ReadReceiptCodec());
The React SDK supports all current standards-track content types, but only text messages are enabled out of the box. Adding support for other standards-track content types requires a bit of configuration.
import {
XMTPProvider,
readReceiptContentTypeConfig,
} from "@xmtp/react-sdk";
const contentTypeConfigs = [
readReceiptContentTypeConfig,
// other content type configs...
];
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<XMTPProvider contentTypeConfigs={contentTypeConfigs}>
<App />
</XMTPProvider>
</StrictMode>,
);
import org.xmtp.android.library.Client
import org.xmtp.android.library.codecs.ReadReceiptCodec
Client.register(codec = ReadReceiptCodec())
Client.register(codec: ReadReceiptCodec())
import 'package:xmtp/src/content/codec_registry.dart';
import 'package:xmtp/src/content/decoded.dart';
import 'package:xmtp/src/content/text_codec.dart';
import 'package:xmtp/src/content/ReadReceiptCodec.dart';
// Registering
var registry = CodecRegistry()..registerCodec(TextCodec());
var codec = ReadReceiptCodec();
codec.setRegistry(registry);
const client = await Client.create(signer, {
env: "production",
codecs: [new ReadReceiptCodec()],
});
Send a read receipt
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
The content of a read receipt message must be an empty object.
await conversation.messages.send({}, ContentTypeReadReceipt);
The content of a read receipt message must be an empty object.
import { useSendMessage } from "@xmtp/react-sdk";
import { ContentTypeReadReceipt } from "@xmtp/content-type-read-receipt";
const { sendMessage } = useSendMessage();
sendMessage(conversation, {}, ContentTypeReadReceipt);
import org.xmtp.android.library.Client
import org.xmtp.android.library.codecs.ReadReceipt
import org.xmtp.android.library.codecs.ContentTypeReadReceipt
import org.xmtp.android.library.messages.SendOptions
// Create a ReadReceipt instance
val readReceipt = ReadReceipt
// Send the read receipt
aliceConversation.send(
content = readReceipt,
options = SendOptions(contentType = ContentTypeReadReceipt),
)
let read = ReadReceipt(timestamp: "2019-09-26T07:58:30.996+0200")
try await conversation.send(
content: read,
options: .init(contentType: ContentTypeReadReceipt)
)
// Creating
var receipt = ReadReceipt();
conversation.send(
content = receipt,
options = SendOptions(contentType = ContentTypeReadReceipt),
)
await bobConversation.send({ readReceipt: {} });
Receive a read receipt
Here's how you can receive a read receipt:
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
if (message.contentType.sameAs(ContentTypeReadReceipt)) {
// The message is a read receipt
const timestamp = message.sent;
}
import { ContentTypeId } from "@xmtp/react-sdk";
import { ContentTypeReadReceipt } from "@xmtp/content-type-read-receipt";
const contentType = ContentTypeId.fromString(message.contentType);
if (ContentTypeReadReceipt.sameAs(contentType)) {
// The message is a read receipt
const timestamp = message.sentAt;
}
getReadReceipt
Use to retrieve the read receipt from a cached conversation. It takes a CachedConversation
object as a parameter and returns the read receipt date, or undefined
, if the conversation has no read receipt.
import { getReadReceipt } from "@xmtp/react-sdk";
const readReceiptDate = getReadReceipt(conversation);
hasReadReceipt
Use to check if a cached conversation has a read receipt. It takes a CachedConversation
object as a parameter and returns true
if the conversation has a read receipt and false
if otherwise.
import { hasReadReceipt } from "@xmtp/react-sdk";
const hasReceipt = hasReadReceipt(conversation);
val message: DecodedMessage = conversation.messages().first()
if (message.encodedContent.type == ContentTypeReadReceipt) {
// The message is a ReadReceipt
val readReceipt: ReadReceipt? = message.content()
if (readReceipt != null) {
println("Message read at: ${readReceipt.timestamp}")
}
}
let content: ReadReceipt = try message.content()
content.timestamp // "2019-09-26T07:58:30.996+0200"
var decodedContent = await registry.decode(encoded);
if (decodedContent.contentType == ContentTypeReadReceipt) {
// The message is a readReceipt
}
if (message.contentTypeId === "xmtp.org/readReceipt:1.0") {
return message.sent; //Date received
}
Display a read receipt
ReadReceipts
have an undefined
or nil
fallback, indicating the message is not expected to be displayed. Refer to how handle unsupported content types section.
Notifications and read receipts
Read receipts have shouldPush
set to false
, which means that read receipts do not trigger push notifications as long as the notification server respects this flag.
Use a read receipt
Generally, a read receipt indicator should be displayed under the message it's associated with. The indicator can include a timestamp. Ultimately, how you choose to display a read receipt indicator is completely up to you.
The read receipt is provided as an empty message whose timestamp provides the data needed for the indicators. Be sure to filter out read receipt empty messages and not display them to users.
You can use a read receipt timestamp to calculate the time since the last message was read. While iterating through messages, you can be sure that the last message was read at the timestamp of the read receipt if the string of the timestamp is lower.