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 { FlatList, StyleSheet, Text, View } from "react-native";
import {
useChatContext,
useMessageReminder,
useQueryReminders,
} from "stream-chat-react-native";
import { ReminderItem } from "./ReminderItem";
const renderItem = ({ item }: { item: ReminderResponse }) => (
<ReminderItem {...item} />
);
const renderEmptyComponent = (
<Text style={styles.emptyContainer}>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 onRefresh = useCallback(async () => {
await client.reminders.queryNextReminders();
}, [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 add a reminder indicator in the header by passing a custom MessageHeader to Channel:

import {
MessageFooterProps,
Time,
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 MessageReminderHeader = ({ message }: MessageFooterProps) => {
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;
}
// This is for "Saved for Later"
if (!reminder.remindAt) {
return (
<View>
<Text style={styles.headerTitle}>🔖 Saved for Later</Text>
</View>
);
}
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>
);
}
};
const styles = StyleSheet.create({
headerContainer: {
flexDirection: "row",
alignItems: "center",
},
headerTitle: {
fontSize: 14,
fontWeight: "500",
marginLeft: 4,
},
});