Reminders System - nself-org/nchat GitHub Wiki
Version: 1.0.0 Last Updated: February 1, 2026 Status: Production Ready ✅
- Overview
- Features
- Architecture
- Components
- API Reference
- GraphQL Schema
- Usage Examples
- Bot Commands
- Database Schema
- Testing
The Reminders System provides a complete Slack-like reminder functionality for nself-chat. Users can set reminders on messages, create custom reminders, and configure recurring reminders with flexible scheduling options.
- Message Reminders: Set reminders on any message in any channel
- Custom Reminders: Create standalone reminders with custom text
- Recurring Reminders: Daily, weekly, monthly, or yearly recurrence
- Quick Time Selection: Preset time options (20 minutes, 1 hour, tomorrow, etc.)
- Snooze Functionality: Postpone reminders temporarily
- Multi-Channel Notifications: In-app, desktop, and email notifications
-
Natural Language:
/remindcommand with natural language parsing - Timezone Support: Full timezone awareness for global teams
- Bulk Operations: Complete or delete multiple reminders at once
-
Message Reminder
- Remind about a specific message
- Jump to message when reminder fires
- Shows message preview and author
-
Custom Reminder
- Standalone reminder with custom text
- No message association
- Flexible notes and descriptions
-
Follow-up Reminder
- Special type for follow-up tasks
- Can be linked to channels or threads
- Optimized for task management
-
Quick Presets:
- In 20 minutes
- In 1 hour
- In 3 hours
- Tomorrow (9:00 AM)
- Next week (Monday 9:00 AM)
-
Custom Date/Time:
- Date picker with calendar
- Time picker with hours/minutes
- Timezone selector (IANA format)
- Future validation
- Frequencies: Daily, Weekly, Monthly, Yearly
- Interval: Repeat every N days/weeks/months/years
- Days of Week: Select specific days (for weekly)
- End Conditions: End date or occurrence count
- Auto-generation: Creates next occurrence on completion
- List All Reminders: View all upcoming and completed reminders
- Filter & Search: Filter by status, type, channel; search content
- Group By: Group by date (Today, This Week, Later) or by channel
- Edit Reminder: Modify time, content, recurrence settings
- Delete Reminder: Permanently remove a reminder
- Mark Complete: Mark as done (keeps in history)
- Snooze Reminder: Postpone for 10m, 30m, 1h, or custom duration
- Bulk Actions: Complete or delete multiple reminders
-
In-App Notification
- Toast notification when reminder is due
- Shows content and quick actions
- Click to jump to message/channel
-
Desktop Notification
- Native OS notification
- Requires permission
- Customizable sound and duration
-
Email Notification (Optional)
- Email sent when reminder is due
- Includes message preview and link
- Configurable in user preferences
-
/remindCommand: Natural language reminder creation -
Examples:
/remind me in 2 hours to review the PR/remind me tomorrow at 9am to call John/remind @john in 1 hour about the meeting/remind #general in 30 minutes to join standup
Frontend:
├── React 19.0.0 (with hooks)
├── Zustand 5.0.3 (state management)
├── Radix UI (components)
├── TailwindCSS 3.4.17 (styling)
└── Framer Motion 11.18.0 (animations)
Backend:
├── Next.js 15.1.6 API Routes
├── Hasura GraphQL Engine
├── PostgreSQL (via nself CLI)
└── GraphQL Subscriptions (real-time)
Notifications:
├── Browser Notification API
├── Custom toast system
└── Email service integration
User Action (UI)
↓
Component (React)
↓
Hook (useReminders)
↓
Store (Zustand) ←→ GraphQL Client (Apollo)
↓ ↓
Local State Hasura GraphQL
↓
PostgreSQL
↓
Subscriptions
↓
Real-time Updates
src/
├── components/reminders/
│ ├── index.ts # Exports
│ ├── set-reminder-modal.tsx # Main creation/edit modal
│ ├── reminders-list.tsx # List view with filters
│ ├── reminder-item.tsx # Individual reminder card
│ ├── reminder-notification.tsx # Notification components
│ ├── reminder-time-picker.tsx # Date/time picker
│ └── quick-remind.tsx # Quick time selection
├── lib/reminders/
│ ├── index.ts # Exports
│ ├── reminder-store.ts # Zustand store
│ └── use-reminders.ts # Main React hook
├── graphql/
│ └── reminders.ts # GraphQL queries/mutations
├── app/api/reminders/
│ └── route.ts # REST API endpoints
└── types/
└── reminder.ts # TypeScript types
Purpose: Primary UI for creating and editing reminders
Props:
interface SetReminderModalProps {
open?: boolean
onOpenChange?: (open: boolean) => void
userId: string
messageId?: string // For message reminders
channelId?: string
initialContent?: string
editingReminder?: Reminder | null
onSuccess?: (reminder: Reminder) => void
onCancel?: () => void
}Features:
- Quick time presets (tabs)
- Custom date/time picker
- Recurring reminder configuration
- Message preview (for message reminders)
- Real-time validation
- Preview panel
Usage:
import { SetReminderModal } from '@/components/reminders'
;<SetReminderModal
open={isOpen}
onOpenChange={setIsOpen}
userId={currentUser.id}
messageId={message.id}
onSuccess={(reminder) => {
console.log('Reminder created:', reminder)
}}
/>Purpose: Display and manage all reminders
Props:
interface RemindersListProps {
userId: string
channelId?: string // Filter to specific channel
onEdit?: (reminder: Reminder) => void
onCreateNew?: () => void
showFilters?: boolean
showSearch?: boolean
showTabs?: boolean
maxHeight?: string | number
emptyMessage?: string
className?: string
}Features:
- Tabbed view (Upcoming / Completed)
- Search and filter bar
- Group by date or channel
- Bulk actions (complete all, delete all)
- Collapsible groups
- Empty states
Usage:
import { RemindersList } from '@/components/reminders'
;<RemindersList
userId={currentUser.id}
showFilters={true}
showSearch={true}
maxHeight="600px"
onEdit={(reminder) => openEditModal(reminder)}
/>Purpose: Display when reminder is due
Components:
-
ReminderNotification: Full-featured notification -
ReminderToast: Toast-style notification -
ReminderBell: Badge with count -
ReminderNotificationContainer: Container for active notifications
Features:
- Auto-dismiss or persistent
- Quick actions (Complete, Snooze, Dismiss)
- Sound alerts
- Desktop notifications
- Click to navigate
Usage:
import { ReminderNotificationContainer } from '@/components/reminders'
// Add to your app layout
;<ReminderNotificationContainer userId={currentUser.id} />Purpose: Quick reminder creation from messages
Components:
-
QuickRemind: Full dropdown menu -
QuickRemindButtons: Button grid -
MessageQuickRemind: Message action integration
Features:
- Preset time options
- One-click reminder creation
- Message context aware
- Custom time option
Usage:
import { MessageQuickRemind } from '@/components/reminders'
;<MessageQuickRemind
messageId={message.id}
channelId={channel.id}
messageContent={message.content}
userId={currentUser.id}
/>Base URL: /api/reminders
Fetch reminders for authenticated user.
Query Parameters:
-
status: Filter by status (pending,completed,dismissed,snoozed) -
channelId: Filter by channel ID -
type: Filter by type (message,custom,followup) -
limit: Maximum results (default 50, max 100) -
offset: Pagination offset
Response:
{
"success": true,
"data": {
"reminders": [
{
"id": "uuid",
"content": "Review PR",
"remind_at": "2026-02-01T15:00:00Z",
"status": "pending",
"type": "custom"
}
],
"total": 42,
"limit": 50,
"offset": 0
}
}Create a new reminder.
Request Body:
{
"messageId": "uuid (optional)",
"channelId": "uuid (optional)",
"content": "Reminder text",
"note": "Additional note (optional)",
"remindAt": "2026-02-01T15:00:00Z",
"timezone": "America/New_York",
"type": "custom",
"isRecurring": false,
"recurrenceRule": {
"frequency": "daily",
"interval": 1,
"endDate": "2026-03-01T00:00:00Z"
}
}Response:
{
"success": true,
"data": {
"reminder": {
/* full reminder object */
},
"message": "Reminder created successfully"
}
}Perform actions on existing reminders.
Request Body:
{
"action": "complete | dismiss | snooze | unsnooze",
"id": "uuid",
"snoozeDuration": 3600000 // milliseconds (for snooze)
}Update an existing reminder.
Request Body:
{
"id": "uuid",
"content": "Updated text",
"remindAt": "2026-02-02T10:00:00Z"
}Delete a reminder.
Query Parameters:
-
id: Reminder ID
type nchat_reminders {
id: uuid!
user_id: uuid!
message_id: uuid
channel_id: uuid
content: String!
note: String
remind_at: timestamptz!
timezone: String!
status: String!
type: String!
is_recurring: Boolean!
recurrence_rule: jsonb
completed_at: timestamptz
snoozed_until: timestamptz
snooze_count: Int!
created_at: timestamptz!
updated_at: timestamptz!
user: nchat_users!
message: nchat_messages
channel: nchat_channels
}query GetReminders($userId: uuid!, $status: String) {
nchat_reminders(
where: { user_id: { _eq: $userId }, status: { _eq: $status } }
order_by: { remind_at: asc }
) {
...ReminderFragment
}
}
query GetDueReminders($userId: uuid!, $now: timestamptz!) {
nchat_reminders(
where: { user_id: { _eq: $userId }, status: { _eq: "pending" }, remind_at: { _lte: $now } }
) {
...ReminderFragment
}
}mutation CreateReminder($userId: uuid!, $content: String!, $remindAt: timestamptz!) {
insert_nchat_reminders_one(
object: {
user_id: $userId
content: $content
remind_at: $remindAt
timezone: "UTC"
status: "pending"
type: "custom"
}
) {
...ReminderFragment
}
}
mutation CompleteReminder($id: uuid!) {
update_nchat_reminders_by_pk(
pk_columns: { id: $id }
_set: { status: "completed", completed_at: "now()" }
) {
...ReminderFragment
}
}
mutation SnoozeReminder($id: uuid!, $snoozedUntil: timestamptz!) {
update_nchat_reminders_by_pk(
pk_columns: { id: $id }
_set: { status: "snoozed", snoozed_until: $snoozedUntil, remind_at: $snoozedUntil }
_inc: { snooze_count: 1 }
) {
...ReminderFragment
}
}subscription ReminderDue($userId: uuid!, $now: timestamptz!) {
nchat_reminders(
where: { user_id: { _eq: $userId }, status: { _eq: "pending" }, remind_at: { _lte: $now } }
) {
...ReminderFragment
}
}import { useReminders } from '@/lib/reminders/use-reminders'
function MessageActions({ message, user }) {
const { setReminderForMessage } = useReminders({ userId: user.id })
const handleRemindIn1Hour = async () => {
const remindAt = new Date(Date.now() + 60 * 60 * 1000)
await setReminderForMessage({
messageId: message.id,
channelId: message.channel_id,
content: `Remind me about this message`,
remindAt,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
})
}
return <button onClick={handleRemindIn1Hour}>Remind me in 1 hour</button>
}import { useReminders } from '@/lib/reminders/use-reminders'
function DailyStandupReminder({ user }) {
const { createReminder } = useReminders({ userId: user.id })
const setupDailyReminder = async () => {
const tomorrow9am = new Date()
tomorrow9am.setDate(tomorrow9am.getDate() + 1)
tomorrow9am.setHours(9, 0, 0, 0)
await createReminder({
content: 'Daily standup meeting',
note: 'Join the team standup',
remindAt: tomorrow9am,
timezone: 'America/New_York',
type: 'custom',
isRecurring: true,
recurrenceRule: {
frequency: 'daily',
interval: 1,
daysOfWeek: [1, 2, 3, 4, 5], // Monday-Friday
},
})
}
return <button onClick={setupDailyReminder}>Setup Daily Standup</button>
}import { RemindersList } from '@/components/reminders'
import { SetReminderModal } from '@/components/reminders'
import { useState } from 'react'
function RemindersPage({ user }) {
const [isModalOpen, setIsModalOpen] = useState(false)
const [editingReminder, setEditingReminder] = useState(null)
return (
<div>
<RemindersList
userId={user.id}
showFilters={true}
showSearch={true}
onEdit={(reminder) => {
setEditingReminder(reminder)
setIsModalOpen(true)
}}
onCreateNew={() => setIsModalOpen(true)}
/>
<SetReminderModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
userId={user.id}
editingReminder={editingReminder}
onSuccess={() => {
setIsModalOpen(false)
setEditingReminder(null)
}}
/>
</div>
)
}import { useRemindersCount } from '@/lib/reminders/use-reminders'
import { Bell } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
function HeaderBell({ user }) {
const { count } = useRemindersCount(user.id)
return (
<button className="relative">
<Bell className="h-5 w-5" />
{count > 0 && (
<Badge className="absolute -right-1 -top-1">{count > 99 ? '99+' : count}</Badge>
)}
</button>
)
}Natural language reminder creation via bot commands.
/remind [target] [time] [message]
Personal Reminders:
/remind me in 2 hours to review the PR
/remind me tomorrow at 9am to call John
/remind me next week about the deadline
/remind me on Friday at 3pm to submit report
Mention Reminders:
/remind @john in 1 hour about the meeting
/remind @sarah tomorrow to review the document
Channel Reminders:
/remind #general in 30 minutes to join standup
/remind #dev-team tomorrow at 10am about deployment
-
Relative:
in X minutes/hours/days/weeks -
Specific Time:
tomorrow at 9am,Friday at 3pm -
Date:
on February 15,on 2026-02-15 -
Combined:
next Monday at 2pm
/remind me every day at 9am to check emails
/remind #team every Monday at 10am for standup
/remind me every week to submit timesheet
CREATE TABLE nchat_reminders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES nchat_users(id) ON DELETE CASCADE,
message_id uuid REFERENCES nchat_messages(id) ON DELETE SET NULL,
channel_id uuid REFERENCES nchat_channels(id) ON DELETE SET NULL,
content text NOT NULL,
note text,
remind_at timestamptz NOT NULL,
timezone text NOT NULL DEFAULT 'UTC',
status text NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'completed', 'dismissed', 'snoozed')),
type text NOT NULL DEFAULT 'custom'
CHECK (type IN ('message', 'custom', 'followup')),
is_recurring boolean NOT NULL DEFAULT false,
recurrence_rule jsonb,
completed_at timestamptz,
snoozed_until timestamptz,
snooze_count integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Indexes
CREATE INDEX idx_reminders_user_status ON nchat_reminders(user_id, status);
CREATE INDEX idx_reminders_remind_at ON nchat_reminders(remind_at);
CREATE INDEX idx_reminders_message ON nchat_reminders(message_id) WHERE message_id IS NOT NULL;
CREATE INDEX idx_reminders_channel ON nchat_reminders(channel_id) WHERE channel_id IS NOT NULL;
CREATE INDEX idx_reminders_due ON nchat_reminders(user_id, status, remind_at)
WHERE status = 'pending';table: nchat_reminders
permissions:
- role: user
select:
filter:
user_id: { _eq: X-Hasura-User-Id }
columns: '*'
insert:
check:
user_id: { _eq: X-Hasura-User-Id }
columns:
- user_id
- message_id
- channel_id
- content
- note
- remind_at
- timezone
- type
- is_recurring
- recurrence_rule
update:
filter:
user_id: { _eq: X-Hasura-User-Id }
columns:
- content
- note
- remind_at
- timezone
- status
- is_recurring
- recurrence_rule
- completed_at
- snoozed_until
- snooze_count
delete:
filter:
user_id: { _eq: X-Hasura-User-Id }// Example: reminder-store.test.ts
import { useReminderStore } from '@/lib/reminders/reminder-store'
describe('ReminderStore', () => {
it('should add a reminder', () => {
const { addReminder, reminders } = useReminderStore.getState()
const newReminder = {
id: '1',
content: 'Test reminder',
status: 'pending',
// ... other fields
}
addReminder(newReminder)
expect(reminders).toContainEqual(newReminder)
})
it('should update reminder status', () => {
const { updateReminder, getReminderById } = useReminderStore.getState()
updateReminder('1', { status: 'completed' })
const reminder = getReminderById('1')
expect(reminder?.status).toBe('completed')
})
})// Example: reminder-flow.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { SetReminderModal } from '@/components/reminders'
describe('Reminder Creation Flow', () => {
it('should create a reminder successfully', async () => {
render(<SetReminderModal open={true} userId="user-1" />)
// Enter content
const contentInput = screen.getByPlaceholderText('Enter your reminder...')
fireEvent.change(contentInput, { target: { value: 'Test reminder' } })
// Select time preset
const in1HourButton = screen.getByText('In 1 hour')
fireEvent.click(in1HourButton)
// Submit
const submitButton = screen.getByText('Set Reminder')
fireEvent.click(submitButton)
await waitFor(() => {
expect(screen.getByText('Reminder created successfully')).toBeInTheDocument()
})
})
})- Smart Reminders: AI-powered suggestions based on message content
- Team Reminders: Remind multiple people at once
- Follow-up Threads: Auto-create thread on reminder
- Integration: Sync with Google Calendar, Outlook
- Voice Reminders: Voice command support
- Geofencing: Location-based reminders
- Priority Queue: Urgent vs. normal reminders
- Templates: Pre-configured reminder templates
If upgrading from a previous system:
- Database Migration: Run schema migration
- Data Migration: Import existing reminders
- Update Imports: Update component imports
- Test: Verify all features work
- Deploy: Roll out to production
For issues, questions, or feature requests:
- GitHub Issues: nself-chat/issues
- Documentation: docs/Reminders-System.md
- Email: [email protected]
Built with ❤️ by the nself-chat team