export type ReminderState = {
channel_cid: string;
created_at: Date;
message: MessageResponse | null;
message_id: string;
remind_at: Date | null;
timeLeftMs: number | null;
updated_at: Date;
user: UserResponse | null;
user_id: string;
};Message Reminders
Message reminders let users revisit important messages later. With a timestamp, the user receives a notification at the scheduled time. Without one, the reminder acts like a bookmark.
Best Practices
- Enable Push V3 and the reminder event before testing reminder delivery.
- Keep reminder offsets limited to reduce UI clutter.
- Use
useMessageReminderfor per-message updates instead of manual polling. - Sort reminders by
remind_atfor predictable list ordering. - Clear reminder indicators when a reminder is deleted or expired.
Push Notifications Setup
Reminders require Push V3. In the Stream Dashboard, go to Push Notifications and click Upgrade to V3.

Then, in each configuration, enable notification.reminder_due in Configure Push Notification Templates.
Message Reminder UI Interaction
Users create, update, or delete reminders from the message actions menu.
The reminder indicator appears in the message UI (or disappears when deleted).
Get Message Reminder Data
The SDK uses client.reminders for CRUD. It stores a reactive map of message IDs to Reminder instances. Each Reminder extends ReminderResponse with timeLeftMs.
- Get a reminder for a specific message
Use useMessageReminder to get the Reminder instance for a message ID.
You can subscribe at two levels:
- Subscribe to a specific reminder’s state
import { useMessageReminder, useStateStore } from "stream-chat-react-native";
import type { LocalMessage, ReminderState } from "stream-chat";
const reminderStateSelector = (state: ReminderState) => ({
timeLeftMs: state.timeLeftMs,
});
const Component = ({ message }: { message: LocalMessage }) => {
// access the message reminder instance
const reminder = useMessageReminder(message.id);
const { timeLeftMs } =
useStateStore(reminder?.state, reminderStateSelector) ?? {};
};timeLeftMs updates more frequently as the deadline approaches (daily → hourly → minutely) and less frequently afterward.
- Subscribe to reminders pagination
Pagination is handled by RemindersPaginator. Use useQueryReminders to fetch pages and react to updates and deletions.
Pagination returns ReminderResponse objects, not Reminder instances.
import { useCallback, useEffect } from "react";
import { ActivityIndicator, FlatList, Text, View } from "react-native";
import type { ReminderResponse } from "stream-chat";
import { useChatContext, useQueryReminders } from "stream-chat-react-native";
import { ReminderItem } from "./ReminderItem";
const renderItem = ({ item }: { item: ReminderResponse }) => (
<ReminderItem {...item} />
);
const renderEmptyComponent = <Text>No reminders available</Text>;
const RemindersList = () => {
const { client } = useChatContext();
const { data, isLoading, loadNext } = useQueryReminders();
useEffect(() => {
client.reminders.paginator.filters = {};
client.reminders.paginator.sort = { remind_at: 1 };
}, [client.reminders]);
const renderFooter = useCallback(() => {
if (isLoading) {
return (
<ActivityIndicator size={"small"} style={{ marginVertical: 16 }} />
);
}
}, [isLoading]);
return (
<View style={{ flex: 1 }}>
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
data={data}
keyExtractor={(item) => item.message.id}
renderItem={renderItem}
ListEmptyComponent={renderEmptyComponent}
ListFooterComponent={renderFooter}
onEndReached={loadNext}
/>
</View>
);
};Message Reminder Configuration
Configure which reminder offsets users can select:
const minute = 60 * 1000;
client.reminders.updateConfig({
scheduledOffsetsMs: [30 * minute, 60 * minute],
});You can also set when the reminder stops refreshing “time since due”:
const day = 24 * 60 * 60 * 1000;
client.reminders.updateConfig({
stopTimerRefreshBoundaryMs: day,
});Reminder indicator on message
You can customize reminder and saved-for-later indicators by passing dedicated header overrides to Channel:

import {
Time,
useMessageContext,
useMessageReminder,
useStateStore,
useTranslationContext,
} from "stream-chat-react-native";
import { ReminderState } from "stream-chat";
import { StyleSheet, Text, View } from "react-native";
const reminderStateSelector = (state: ReminderState) => ({
timeLeftMs: state.timeLeftMs,
});
export const CustomMessageReminderHeader = () => {
const { message } = useMessageContext();
const messageId = message?.id ?? "";
const reminder = useMessageReminder(messageId);
const { timeLeftMs } =
useStateStore(reminder?.state, reminderStateSelector) ?? {};
const { t } = useTranslationContext();
const stopRefreshBoundaryMs = reminder?.timer.stopRefreshBoundaryMs;
const stopRefreshTimeStamp =
reminder?.remindAt && stopRefreshBoundaryMs
? reminder?.remindAt.getTime() + stopRefreshBoundaryMs
: undefined;
const isBehindRefreshBoundary =
!!stopRefreshTimeStamp && new Date().getTime() > stopRefreshTimeStamp;
if (!reminder) {
return null;
}
if (reminder.remindAt && timeLeftMs != null) {
return (
<View style={styles.headerContainer}>
<Time height={16} width={16} />
<Text style={styles.headerTitle}>
{isBehindRefreshBoundary
? t("Due since {{ dueSince }}", {
dueSince: t("timestamp/ReminderNotification", {
timestamp: reminder.remindAt,
}),
})
: t("Due {{ timeLeft }}", {
timeLeft: t("duration/Message reminder", {
milliseconds: timeLeftMs,
}),
})}
</Text>
</View>
);
}
return null;
};
export const CustomMessageSavedForLaterHeader = () => {
const { t } = useTranslationContext();
return (
<View>
<Text style={styles.headerTitle}>{t("Saved For Later")}</Text>
</View>
);
};
const styles = StyleSheet.create({
headerContainer: {
flexDirection: "row",
alignItems: "center",
},
headerTitle: {
fontSize: 14,
fontWeight: "500",
marginLeft: 4,
},
});
<Channel
MessageReminderHeader={CustomMessageReminderHeader}
MessageSavedForLaterHeader={CustomMessageSavedForLaterHeader}
/>;