Support replies in your app built with XMTP
Use the reply content type to support quote replies in your app. A reply is a method to directly respond to a specific message in a conversation. Users can select and reply to a particular message instead of sending a new one.
You're welcome to provide feedback by commenting on the Proposal for Reply content type XIP idea.
Use a local database for performance
Use a local database to store replies. This will enable your app to performantly display a reply with its referenced message when rendering message lists.
To learn more about using a local database, see Use local-first architecture.
Configure the content type
In some SDKs, the ReplyCodec
is already included in the SDK. If not, you can install the package using the following command:
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
In the JavaScript SDK, you need to import this package first.
npm i @xmtp/content-type-reply
After importing the package, you can register the codec.
import { ContentTypeReply, ReplyCodec } from "@xmtp/content-type-reply";
// Create the XMTP client
const xmtp = await Client.create(signer, { env: "dev" });
xmtp.registerCodec(new ReplyCodec());
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,
replyContentTypeConfig,
} from "@xmtp/react-sdk";
const contentTypeConfigs = [
replyContentTypeConfig,
];
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<XMTPProvider contentTypeConfigs={contentTypeConfigs}>
<App />
</XMTPProvider>
</StrictMode>,
);
import org.xmtp.android.library.codecs.ReplyCodec
Client.register(codec = ReplyCodec())
Client.register(codec: ReplyCodec())
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/reply_codec.dart';
// Registering
var registry = CodecRegistry()..registerCodec(TextCodec());
var codec = ReplyCodec();
codec.setRegistry(registry);;
const client = await Client.create(signer, {
env: "production",
codecs: [new ReplyCodec()],
});
Send a reply
Once you've created a reply, you can send it. Replies are represented as objects with two keys:
reference
: ID of the message being replied tocontent
: String representation of the reply
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
import { ContentTypeText } from "@xmtp/xmtp-js";
import { ContentTypeReply } from "@xmtp/content-type-reply";
const reply: Reply = {
reference: someMessageID,
contentType: ContentTypeText
content: "I concur",
};
await conversation.send(reply, {
contentType: ContentTypeReply,
});
import { useSendMessage } from "@xmtp/react-sdk";
import { ContentTypeReply } from "@xmtp/content-type-reply";
const { sendMessage } = useSendMessage();
const replyContent = {
content: "Your reply content here",
contentType: ContentTypeText,
reference: "originalMessageXmtpId",
};
sendMessage(conversation, replyContent, ContentTypeReply);
import org.xmtp.android.library.codecs.ContentTypeReply
import org.xmtp.android.library.codecs.ContentTypeText
import org.xmtp.android.library.codecs.Reply
// Assuming aliceConversation and messageToReact are already defined
val reply = Reply(
reference = messageToReact.id,
content = "Hello",
contentType = ContentTypeText
)
aliceConversation.send(
content = reply,
options = SendOptions(contentType = ContentTypeReply),
)
let reply = Reply(
reference: messageToReply.id,
content: "Hello",
contentType: ContentTypeText
)
try await conversation.send(
content: reply,
options: .init(contentType: ContentTypeReply)
)
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/reply_codec.dart';
var parentMessageId = "abc123"; // The ID of the message you're replying to
var replyContent = DecodedContent(contentTypeText, "This is my reply"); // The content of your reply
// Create a Reply instance
var reply = Reply(parentMessageId, replyContent);
// Send the reply
await client.sendMessage(conversation, reply, contentType: contentTypeReply);
// Assuming you have a conversation object and the ID of the message you're replying to
const replyContent = {
reply: {
reference: messageId, // ID of the message you're replying to
content: {
text: "This is a reply", // Content of the reply
},
},
};
await conversation.send(replyContent);
Receive the content type
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
if (message.contentType.sameAs(ContentTypeReply)) {
// We've got a reply.
const reply: Reply = message.content;
}
import { ContentTypeId } from "@xmtp/react-sdk";
import { ContentTypeReply } from "@xmtp/content-type-reply";
const contentType = ContentTypeId.fromString(message.contentType);
if (ContentTypeReply.sameAs(contentType)) {
// We've got a reply.
const reply = message.content as Reply;
// Use reply...
}
getReplies
Use to retrieve all replies for a given message. It takes a message object and returns an array of reply IDs.
import { getReplies } from "@xmtp/react-sdk";
const replies = getReplies(message);
hasReply
Use to check if a given message has any replies. It takes a message object and returns a boolean indicating whether the message has replies.
import { hasReply } from "@xmtp/react-sdk";
const messageHasReply = hasReply(message);
getOriginalMessageFromReply
Use to retrieve the original message the reply is responding to. It takes a reply message object and returns the original message object.
import { getOriginalMessageFromReply } from "@xmtp/react-sdk";
const originalMessage = await getOriginalMessageFromReply(replyMessage, db);
if (encodedContent.type == ContentTypeReply) {
// The message is a reply
val reply: Reply? = message.content()
println("Reply to: ${reply?.reference}")
println("Content: ${reply?.content}")
}
let content: Reply = try message.content()
var decodedContent = await registry.decode(encoded);
if (decodedContent.contentType == contentTypeReply) {
// The message is a reply.
}
if (message.contentTypeId === "xmtp.org/reply:1.0") {
const reply = message.content();
if (reply) {
const replyContent: ReplyContent = reply;
const replyContentType = replyContent.contentType;
const codec = client.codecRegistry[replyContentType];
const actualReplyContent = codec.decode(replyContent.content);
}
}
To handle unsupported content types, refer to the fallback section.
Display the reply
How you choose to display replies in your app is up to you. It might be useful to look at the user experience for replies in popular apps such as Telegram and Discord. For example, in Discord, users can reply to individual messages, and the reply provides a link to the original message.
Note that the user experience of replies in iMessage and Slack follows more of a threaded pattern, where messages display in logical groupings, or threads. This reply content type doesn't support the threaded pattern. If you'd like to request support for a threaded reply pattern, post an XIP idea.