Overview
The reminder system helps caregivers stay on schedule with feeds, diaper changes, medications, and custom activities. Reminders can be time-based or event-based, with support for quiet hours and snooze options.
Schema Structure
reminderRules : defineTable ({
babyId: v . id ( "babyProfiles" ),
category: v . string (),
title: v . string (),
triggerType: v . string (),
triggerConfig: v . optional ( v . any ()),
enabled: v . boolean (),
quietHoursStart: v . optional ( v . number ()),
quietHoursEnd: v . optional ( v . number ()),
snoozeOptions: v . optional ( v . any ()),
createdAt: v . string (),
})
. index ( "by_babyId" , [ "babyId" ])
. index ( "by_babyId_category" , [ "babyId" , "category" ])
Reminder Fields
Category of the reminder: "feed", "diaper", "medicine", "vaccine", or "custom"
Display title for the reminder (e.g., “Feed Baby”, “Check Diaper”, “Give Medicine”)
How the reminder is triggered:
"fixedTimes": At specific times of day
"afterLastEventType": X hours after last activity
Configuration specific to the trigger type (see below)
Whether the reminder is active. Defaults to true
Hour (0-23) when quiet hours begin. Reminders won’t fire during quiet hours
Hour (0-23) when quiet hours end
Configuration for snooze behavior (minutes, max snoozes, etc.)
Reminder Categories
export const REMINDER_CATEGORIES = {
FEED: "feed" ,
DIAPER: "diaper" ,
MEDICINE: "medicine" ,
VACCINE: "vaccine" ,
CUSTOM: "custom" ,
} as const ;
Trigger Types
Fixed Times
Reminder fires at specific times each day:
type FixedTimesTriggerConfig = {
times : string []; // Array of "HH:MM" strings
};
await createReminderRule ({
babyId ,
category: "feed" ,
title: "Feed Baby" ,
triggerType: "fixedTimes" ,
triggerConfig: {
times: [ "08:00" , "12:00" , "16:00" , "20:00" ],
},
enabled: true ,
});
After Last Event
Reminder fires X hours after the last activity of a specific type:
type AfterLastEventTriggerConfig = {
lastEventType : string ; // e.g., "FEED_BOTTLE", "DIAPER"
intervalHours : number ; // Hours to wait after last event
};
await createReminderRule ({
babyId ,
category: "feed" ,
title: "Time for Next Feed" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "FEED_BOTTLE" ,
intervalHours: 3 ,
},
enabled: true ,
});
Creating Reminder Rules
export const createReminderRule = mutation ({
args: {
babyId: v . id ( "babyProfiles" ),
category: v . string (),
title: v . string (),
triggerType: v . string (),
triggerConfig: v . optional ( v . any ()),
enabled: v . optional ( v . boolean ()),
quietHoursStart: v . optional ( v . number ()),
quietHoursEnd: v . optional ( v . number ()),
snoozeOptions: v . optional ( v . any ()),
},
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
await requireBabyAccess ( ctx , args . babyId , user . _id );
const id = await ctx . db . insert ( "reminderRules" , {
... args ,
enabled: args . enabled ?? true ,
createdAt: new Date (). toISOString (),
});
return id ;
},
});
Example: Feed Reminder
import { useMutation } from "convex/react" ;
import { api } from "../../convex/_generated/api" ;
const createReminder = useMutation ( api . events . createReminderRule );
await createReminder ({
babyId ,
category: "feed" ,
title: "Feed Baby" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "FEED_BOTTLE" ,
intervalHours: 3 ,
},
enabled: true ,
quietHoursStart: 22 , // 10 PM
quietHoursEnd: 7 , // 7 AM
});
Example: Medicine Reminder
await createReminder ({
babyId ,
category: "medicine" ,
title: "Give Vitamin D Drops" ,
triggerType: "fixedTimes" ,
triggerConfig: {
times: [ "09:00" ], // Once daily at 9 AM
},
enabled: true ,
});
Example: Diaper Check Reminder
await createReminder ({
babyId ,
category: "diaper" ,
title: "Check Diaper" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "DIAPER" ,
intervalHours: 2 ,
},
enabled: true ,
});
Computing Upcoming Reminders
The system computes when reminders are due:
export const computeUpcomingReminders = query ({
args: {
babyId: v . id ( "babyProfiles" ),
now: v . optional ( v . string ()),
},
handler : async ( ctx , args ) => {
const currentTime = args . now ? new Date ( args . now ) : new Date ();
const rules = await ctx . db
. query ( "reminderRules" )
. withIndex ( "by_babyId" , ( q ) => q . eq ( "babyId" , args . babyId ))
. collect ();
const enabledRules = rules . filter (( r ) => r . enabled );
const upcoming : Array <{
rule : any ;
dueTime : string ;
isOverdue : boolean ;
}> = [];
for ( const rule of enabledRules ) {
if ( rule . triggerType === "fixedTimes" && rule . triggerConfig ?. times ) {
for ( const time of rule . triggerConfig . times ) {
const [ hours , minutes ] = time . split ( ":" ). map ( Number );
const dueTime = new Date ( currentTime );
dueTime . setHours ( hours , minutes , 0 , 0 );
if ( dueTime < currentTime ) {
dueTime . setDate ( dueTime . getDate () + 1 );
}
upcoming . push ({
rule ,
dueTime: dueTime . toISOString (),
isOverdue: false ,
});
}
} else if ( rule . triggerType === "afterLastEventType" ) {
const lastEvent = await getLatestEventForType (
ctx ,
args . babyId ,
rule . triggerConfig ?. lastEventType
);
if ( lastEvent ) {
const lastEventTime = new Date ( lastEvent . timestamp );
const intervalMs = ( rule . triggerConfig ?. intervalHours || 3 ) * 60 * 60 * 1000 ;
const dueTime = new Date ( lastEventTime . getTime () + intervalMs );
upcoming . push ({
rule ,
dueTime: dueTime . toISOString (),
isOverdue: dueTime < currentTime ,
});
}
}
}
return upcoming . sort (( a , b ) =>
new Date ( a . dueTime ). getTime () - new Date ( b . dueTime ). getTime ()
). slice ( 0 , 10 );
},
});
Usage on Dashboard
const upcomingReminders = useQuery (
api . events . computeUpcomingReminders ,
babyId ? { babyId } : "skip"
);
const nextReminder = upcomingReminders ?.[ 0 ] ?? null ;
The next reminder is displayed prominently on the dashboard:
{ nextReminder && (
< div className = { `rounded-[20px] p-5 border mb-8 ${
nextReminder . isOverdue
? "bg-alert-red/5 border-alert-red/20"
: ` ${ genderTheme . bg } ${ genderTheme . border } `
} ` } >
< h3 className = "text-sm font-bold text-muted uppercase tracking-wider mb-2" > Next Reminder </ h3 >
< div className = "flex items-center justify-between" >
< div >
< div className = "font-bold text-espresso" > { nextReminder . rule . title } </ div >
< div className = { `text-sm ${ nextReminder . isOverdue ? "text-alert-red" : "text-muted" } ` } >
{ nextReminder . isOverdue
? "Overdue"
: new Date ( nextReminder . dueTime ). toLocaleTimeString ( "en-IN" , { hour: "2-digit" , minute: "2-digit" , hour12: true }) }
</ div >
</ div >
< span className = { `material-symbols-outlined ${ nextReminder . isOverdue ? "text-alert-red" : genderTheme . text } ` } >
notifications
</ span >
</ div >
</ div >
)}
Quiet Hours
Prevent reminders during sleep hours:
await createReminder ({
babyId ,
category: "feed" ,
title: "Feed Baby" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "FEED_BOTTLE" ,
intervalHours: 3 ,
},
enabled: true ,
quietHoursStart: 22 , // 10 PM
quietHoursEnd: 7 , // 7 AM
});
Quiet hours are specified in 24-hour format (0-23). Reminders scheduled during quiet hours will be suppressed or delayed until quiet hours end.
Snooze Options
type SnoozeOptions = {
defaultMinutes : number ; // Default snooze duration
allowCustom : boolean ; // Allow custom snooze times
maxSnoozes : number ; // Maximum times to snooze
intervals : number []; // Quick snooze intervals (minutes)
};
await createReminder ({
babyId ,
category: "medicine" ,
title: "Give Medicine" ,
triggerType: "fixedTimes" ,
triggerConfig: { times: [ "09:00" ] },
snoozeOptions: {
defaultMinutes: 10 ,
allowCustom: true ,
maxSnoozes: 3 ,
intervals: [ 5 , 10 , 15 , 30 ],
},
});
Listing Reminder Rules
const reminders = useQuery ( api . events . listReminderRules , { babyId });
// Filter by category
const feedReminders = reminders ?. filter ( r => r . category === "feed" );
const medicineReminders = reminders ?. filter ( r => r . category === "medicine" );
// Filter by enabled status
const active = reminders ?. filter ( r => r . enabled );
const disabled = reminders ?. filter ( r => ! r . enabled );
Updating Reminder Rules
export const updateReminderRule = mutation ({
args: {
id: v . id ( "reminderRules" ),
title: v . optional ( v . string ()),
triggerType: v . optional ( v . string ()),
triggerConfig: v . optional ( v . any ()),
enabled: v . optional ( v . boolean ()),
quietHoursStart: v . optional ( v . number ()),
quietHoursEnd: v . optional ( v . number ()),
snoozeOptions: v . optional ( v . any ()),
},
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
const rule = await ctx . db . get ( args . id );
if ( ! rule ) throw new Error ( "Reminder rule not found" );
await requireBabyAccess ( ctx , rule . babyId , user . _id );
const { id , ... updates } = args ;
await ctx . db . patch ( id , updates );
return id ;
},
});
Enabling/Disabling Reminders
const updateReminder = useMutation ( api . events . updateReminderRule );
// Disable reminder
await updateReminder ({ id: reminderId , enabled: false });
// Re-enable reminder
await updateReminder ({ id: reminderId , enabled: true });
Deleting Reminder Rules
export const deleteReminderRule = mutation ({
args: { id: v . id ( "reminderRules" ) },
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
const rule = await ctx . db . get ( args . id );
if ( ! rule ) throw new Error ( "Reminder rule not found" );
await requireBabyAccess ( ctx , rule . babyId , user . _id );
await ctx . db . delete ( args . id );
},
});
Push Notifications
Reminders can trigger push notifications (requires push subscription setup):
pushSubscriptions : defineTable ({
userId: v . string (),
endpoint: v . string (),
keys: v . object ({
p256dh: v . string (),
auth: v . string (),
}),
createdAt: v . string (),
})
. index ( "by_userId" , [ "userId" ])
. index ( "by_endpoint" , [ "endpoint" ])
Push notification delivery is handled by background jobs that check for due reminders and send notifications to subscribed devices.
Common Reminder Patterns
Newborn Feeds
Daily Medication
Diaper Checks
Multiple Daily Feeds
// Every 3 hours, 24/7
await createReminder ({
babyId ,
category: "feed" ,
title: "Feed Time" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "FEED_BOTTLE" ,
intervalHours: 3 ,
},
enabled: true ,
});
// Once daily at specific time
await createReminder ({
babyId ,
category: "medicine" ,
title: "Vitamin D Drops" ,
triggerType: "fixedTimes" ,
triggerConfig: {
times: [ "09:00" ],
},
enabled: true ,
});
// Every 2 hours during wake hours
await createReminder ({
babyId ,
category: "diaper" ,
title: "Check Diaper" ,
triggerType: "afterLastEventType" ,
triggerConfig: {
lastEventType: "DIAPER" ,
intervalHours: 2 ,
},
quietHoursStart: 20 ,
quietHoursEnd: 7 ,
enabled: true ,
});
// Fixed feeding schedule
await createReminder ({
babyId ,
category: "feed" ,
title: "Scheduled Feed" ,
triggerType: "fixedTimes" ,
triggerConfig: {
times: [ "06:00" , "09:00" , "12:00" , "15:00" , "18:00" , "21:00" ],
},
enabled: true ,
});
Dashboard Next reminder displayed on dashboard
Activity Tracking Reminders trigger based on logged activities
Baby Profiles Each profile has its own reminder rules
Weekly Digests Reminder adherence tracked in digests