Feature spec
IntermediateCategory
notifications-alerts
Build with AI
4–8 hours with FlutterFlow or Lovable
Custom build
1–2 weeks custom dev
Running cost
$0/mo (on-device) · $25/mo if using Supabase pg_cron for web push
Works on
Everything it takes to ship Customizable Reminders — parts, prompts, and real costs.
Customizable reminders need four pieces: a creation form with recurrence options, a local notification scheduler (flutter_local_notifications), a quiet hours gate in the data model, and snooze logic wired to the notification response callback. With FlutterFlow you can ship a working reminder system in 4–6 hours for $0/month — the scheduling library is free and entirely on-device. Server costs only appear if you add web push for a PWA path via Supabase pg_cron.
What Customizable Reminders Actually Are
Customizable reminders let each user set their own schedule for the things that matter to them — a habit at 7 AM every weekday, a medication at noon and 9 PM, a weekly review every Sunday. Unlike push notifications (which the server sends), local reminders fire entirely on the device through the OS notification center, which means they work with the app fully closed and require no server infrastructure. The product decisions are: how rich to make the recurrence options (one-time vs daily vs custom days), whether to support quiet hours, how the notification tap deep-links back into the app, and what the snooze UX feels like.
What users consider table stakes in 2026
- Users can set one-time or repeating reminders — at minimum daily and weekly, ideally custom days of the week
- Notification arrives even when the app is fully closed or the device is in Do Not Disturb (depends on OS priority setting)
- Reminder label and scheduled time are editable after creation without deleting and recreating
- Snooze option appears on notification tap with user-configurable duration (5, 10, or 30 minutes)
- Quiet hours setting blocks reminders during a chosen time range (e.g. 10 PM – 8 AM) per user preference
- List view of all scheduled reminders with a per-reminder enable/disable toggle so users pause without deleting
Anatomy of the Feature
Seven components across three layers. FlutterFlow generates the UI layer and connects to the scheduling library; the recurrence engine and quiet hours logic are where first builds break.
Reminder Creation Form
UITime picker (Flutter's showTimePicker) combined with a label input (max 50 chars), a recurrence selector (one-time / daily / custom days-of-week), and a snooze duration preference. Writes the new reminder to the reminders table in Supabase on save.
Note: Validate label length client-side before save — the notification payload truncates long strings on some Android skins.
Recurrence Engine
DataStores RRULE strings (RFC 5545 format) or cron expressions per reminder row in the rrule column. The flutter_local_notifications plugin calculates the next trigger time from the recurrence pattern at scheduling time.
Note: RRULE gives you maximum flexibility (BYDAY, COUNT, UNTIL). For simple apps, storing a day-of-week bitmask integer is lighter and easier for the AI to generate correctly.
Local Notification Scheduler
BackendThe flutter_local_notifications package schedules AlarmManager intents on Android and UNUserNotificationCenter triggers on iOS. Uses AndroidScheduleMode.exactAllowWhileIdle for precise timing even when the device is in Doze mode. Fires notification payloads when the app is backgrounded or terminated.
Note: Android 13+ (API 33+) requires SCHEDULE_EXACT_ALARM permission requested at runtime — the package requests USE_EXACT_ALARM by default, which is insufficient for apps targeting API 33+.
Notification Payload Handler
BackendThe onDidReceiveNotificationResponse callback receives the notification tap event. It decodes the JSON payload (reminder_id, label, action) and routes the user to the correct reminder detail screen, logging a 'completed' or 'dismissed' action in reminder_logs.
Note: Pass all payload data as a JSON-encoded string in the payload field — dynamic variables resolve to null in the background isolate if passed separately.
Snooze Logic
BackendWhen the user taps Snooze from the notification, a one-time notification is scheduled N minutes out (where N is the user's stored snooze_minutes preference: 5, 10, or 30). A snooze event is inserted into reminder_logs with action='snoozed' and the new fire time.
Note: Snooze scheduling must happen in a background isolate on Android so it works even if the user doesn't open the app after tapping snooze.
Quiet Hours Gate
DataEach user row stores quiet_start and quiet_end as TIME values in UTC. Before scheduling a notification, the scheduler checks whether the calculated next_fire_at falls within the quiet window; if so, it reschedules to quiet_end on the same day (or next day if past midnight).
Note: Always store quiet hours in UTC and convert to local time at display — comparing stored local times after the user travels causes off-by-one hour errors.
Reminders List Screen
UIA ListView.builder showing all reminders for the current user, ordered by next_fire_at. Each row shows the label, recurrence description, next fire time, and a Switch widget. Toggle off calls cancelNotification() and sets is_active = false; toggle on calls scheduleNotification() and sets is_active = true.
Note: Re-query the reminders table after any toggle to keep the list in sync with what is actually scheduled in flutter_local_notifications.
The data model
Two tables cover scheduling and history. Run this in the Supabase SQL editor — the RLS policies ensure users only see and modify their own reminders.
1create table public.reminders (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null,4 label text not null check (char_length(label) <= 50),5 rrule text,6 next_fire_at timestamptz,7 is_active bool not null default true,8 snooze_minutes int not null default 10,9 quiet_start time,10 quiet_end time,11 created_at timestamptz not null default now()12);1314create table public.reminder_logs (15 id uuid primary key default gen_random_uuid(),16 reminder_id uuid references public.reminders(id) on delete cascade not null,17 fired_at timestamptz not null default now(),18 action text not null check (action in ('fired','snoozed','completed','dismissed'))19);2021alter table public.reminders enable row level security;22alter table public.reminder_logs enable row level security;2324create policy "Users manage own reminders"25 on public.reminders for all26 using (auth.uid() = user_id)27 with check (auth.uid() = user_id);2829create policy "Users view own reminder logs"30 on public.reminder_logs for select31 using (32 exists (33 select 1 from public.reminders r34 where r.id = reminder_id and r.user_id = auth.uid()35 )36 );3738create policy "Service role inserts reminder logs"39 on public.reminder_logs for insert40 with check (true);4142create index reminders_user_next_idx43 on public.reminders (user_id, next_fire_at)44 where is_active = true;Heads up: The partial index on active reminders keeps the scheduler query fast even for users with hundreds of historical reminders. The ON DELETE CASCADE on reminder_logs means deleting a reminder automatically cleans up its log history.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
Best path for mobile reminders: FlutterFlow has a dedicated flutter_local_notifications plugin and Custom Actions handle the iOS/Android permission flows. Build the UI visually, drop into Custom Code for the scheduler.
Step by step
- 1In FlutterFlow, add the flutter_local_notifications package under Settings → Pub Dependencies, then add the Supabase package for your reminders table
- 2Build the Reminder Creation Form page visually: add a TimePicker widget, a TextField for the label (max 50 chars), a RadioGroup for recurrence (One-time / Daily / Custom days), and a DropDown for snooze minutes (5/10/30)
- 3Create a Custom Action named scheduleReminder that calls flutter_local_notifications to schedule an AlarmManager-style exact trigger using AndroidScheduleMode.exactAllowWhileIdle, passing label and reminder_id as JSON payload; request notification permission inside this action using requestExactAlarmsPermission() before scheduling
- 4Build the Reminders List page using a Supabase Query for the reminders table filtered by auth.uid(); add a Switch widget on each row wired to a Custom Action that calls cancelNotification() on toggle-off and scheduleReminder() on toggle-on
- 5Add a Custom Action named handleNotificationTap registered in initState that reads onDidReceiveNotificationResponse and navigates to the reminder detail page using the reminder_id from the JSON payload
- 6Test on a real physical device — the FlutterFlow web preview cannot schedule or receive local notifications
Where this path bites
- Android 13+ requires requestExactAlarmsPermission() called in a Custom Action — FlutterFlow doesn't add the SCHEDULE_EXACT_ALARM manifest entry automatically
- Continuous quiet hours across midnight (e.g. 10 PM – 8 AM) requires extra date arithmetic in the Custom Function — FlutterFlow's Date helpers don't handle the day-rollover edge case out of the box
- iOS background scheduling survives force-close only up to the pending notification queue limit; users who force-close repeatedly may miss reminders until they reopen the app
Third-party services you'll need
On-device reminder scheduling (the FlutterFlow path) is entirely free. Costs appear only if you take the web/PWA path requiring server-side push delivery.
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| flutter_local_notifications | On-device scheduler for AlarmManager (Android) and UNUserNotificationCenter (iOS); fires reminders with app closed | Open-source, zero cost | Free |
| Supabase pg_cron | Server-side cron scheduler for web/PWA push path — runs SQL functions on a schedule to trigger reminder push delivery | Not available on Supabase free tier | Included in Supabase Pro $25/mo (approx) |
| web-push npm package | Open-source library for sending Web Push API payloads from a Supabase Edge Function; VAPID keys are self-generated at no cost | Open-source, zero cost | Free — delivery via FCM (Firebase Cloud Messaging) is also free |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
flutter_local_notifications is entirely on-device — no server needed. Supabase free tier covers the reminders table and reminder_logs storage easily at this scale.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Reminders fire on Android emulator but not on physical Android 13+ devices
Symptom: Android 13 (API 33) introduced a stricter exact alarm permission — SCHEDULE_EXACT_ALARM — that apps targeting API 33+ must request at runtime. The flutter_local_notifications package requests USE_EXACT_ALARM by default, which is sufficient for API 31–32 but not API 33+. On a physical Pixel 7 or Samsung Galaxy S23, the scheduled notification is registered but silently never fires.
Fix: In your Custom Action before calling scheduleNotification(), call await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.requestExactAlarmsPermission(). Also verify AndroidScheduleMode.exactAllowWhileIdle is set on the scheduling call, not AndroidScheduleMode.exact.
iOS reminders stop after the user force-closes the app twice
Symptom: iOS monitors battery consumption per app. If a user force-closes (swipes away) an app multiple times in succession, iOS applies background execution limits that prevent future scheduled notifications from waking the app's background isolate. UNUserNotificationCenter's pending notifications survive in the OS queue, but apps that rely on the background isolate to reschedule after each fire lose that rescheduling step.
Fix: Schedule all recurrence instances up front in UNUserNotificationCenter (iOS allows up to 64 pending notifications per app) rather than relying on a background callback to reschedule each occurrence. On app open, check for missed reminders using next_fire_at from Supabase and reschedule the upcoming queue. Document this iOS limitation with an in-app tooltip.
Reminder label shows null in the notification instead of the user's text
Symptom: FlutterFlow Custom Actions pass notification body as a dynamic variable resolved at scheduling time. When flutter_local_notifications fires the notification from the background isolate, any variable that was bound to app state is no longer available — the isolate has no access to the running app's memory, so the label resolves to null.
Fix: Encode all payload data — label, reminder_id, snooze_minutes — as a JSON string in the NotificationDetails payload field when scheduling. In onDidReceiveNotificationResponse, parse the JSON string: final data = jsonDecode(response.payload!); This is the correct pattern for passing data through the notification boundary.
Quiet hours don't block notifications consistently after a timezone change
Symptom: Quiet hours are checked at scheduling time against the device's current local clock. If the user travels to a different timezone after scheduling, the stored quiet_start/quiet_end values (saved as local time strings) no longer match the new local time. A reminder that was blocked at 11 PM local time fires at 11 PM UTC instead when the user is in a +10 timezone.
Fix: Store quiet_start and quiet_end in UTC in the database. On the device, convert the user's preferred local quiet window to UTC before saving. Re-evaluate and reschedule all active reminders whenever the device reports a timezone change (listen to the system timezone change broadcast on Android, or re-evaluate on app foreground on iOS).
Best practices
Request notification permission only after the user taps 'Add Reminder' for the first time — explain the benefit before triggering the system dialog to maximize grant rate
Show an in-app explanatory screen before the system permission dialog: 'We need notification permission to remind you even when the app is closed' with an illustration
Schedule all upcoming recurrence instances (up to 64 on iOS) at creation time rather than relying on a background reschedule callback — this is the only reliable pattern on iOS
Validate label length (50 chars max) client-side and trim silently — notification payload size limits can cause delivery failures for very long titles
Encode all notification payload data as a single JSON string in the payload field; never rely on dynamic app state variables in the background isolate
Re-evaluate and reschedule pending reminders on every app foreground resume — catches missed reminders after OS restarts, updates, or timezone changes
Provide a 'Test reminder' button in settings that schedules a notification 10 seconds out — lets users verify permissions are working without waiting for a real reminder
When You Need Custom Development
FlutterFlow with flutter_local_notifications covers the vast majority of reminder use cases. These scenarios outgrow that pattern:
- Reminders need to sync across devices — a habit set on an iPhone should fire on the user's iPad too, requiring a server-driven push delivery layer beyond local notifications
- Integration with Google Calendar or Apple Calendar is required for two-way sync, which involves separate OAuth flows and event management APIs not covered by flutter_local_notifications
- Enterprise use case where managers set reminders for team members and need override controls, or where all reminder interactions require a compliance audit log
- Healthcare or regulated context requiring proof-of-delivery logging: that a medication reminder fired at exactly 08:00:03 on a specific date with a specific device attestation
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
Do reminders work when the phone is on silent?
Yes — local notifications are delivered to the notification center regardless of the ringer switch. They appear in the lock screen and notification drawer. Whether they play a sound depends on the device's 'Do Not Disturb' settings and the notification channel priority you configure in flutter_local_notifications. Setting importance to AndroidImportance.high bypasses some DND modes on Android.
How do I make reminders repeat every weekday?
Store BYDAY=MO,TU,WE,TH,FR as the RRULE value in the database. On the scheduling side, flutter_local_notifications doesn't parse RRULE natively — calculate the next weekday occurrence in a Custom Function and reschedule after each fire. Alternatively, schedule Monday through Friday as five separate repeating weekly notifications sharing the same reminder_id, and cancel all five when the user disables the reminder.
Why did my reminder stop working after I updated the app?
App updates on Android cancel pending AlarmManager alarms — this is an OS behavior. On app launch after an update, query the reminders table for all active reminders where next_fire_at is in the future and reschedule them. Add this logic in your app's initState or a startup service so it runs automatically after every update.
Can users set reminders from a web browser too?
Web browsers can receive push notifications via the Web Push API, but they cannot schedule future local notifications the way a native app can — the browser has to be running (or the site installed as a PWA) for the service worker to receive them. For a true 'fires even when closed' experience on mobile, a native Flutter app using flutter_local_notifications is the right platform. The PWA path via Lovable is a reasonable compromise for non-critical web reminders.
How do I test reminders without waiting for the scheduled time?
Add a 'Test Reminder' button in your debug menu that schedules a notification 10–15 seconds in the future with the same payload logic as a real reminder. This lets you verify the permission grant, payload decoding, deep-link routing, and snooze behavior without manipulating the device clock. Always test on a real physical device — iOS and Android simulators have limited notification support.
What happens if the user denies notification permission?
The notification is never delivered, but the reminder row is still saved in your database. Show a persistent in-app banner on the reminders list screen when permission is denied: 'Notifications are turned off — your reminders won't arrive. Tap here to open Settings and enable them.' Use Flutter's permission_handler package to detect the current status and link directly to the app's system settings page.
Can I add custom sounds and vibration patterns to reminders?
Yes. On Android, create a custom NotificationChannel with a sound URI pointing to a .mp3 in the res/raw folder and a vibration pattern array. On iOS, set UNNotificationSound.named() with a bundled .aiff or .caf file. In flutter_local_notifications, configure these in AndroidNotificationDetails and DarwinNotificationDetails. Note: custom sounds must be included in the app bundle — you cannot load them from a URL at notification time.
How many reminders can one user have?
iOS limits each app to 64 pending scheduled notifications in UNUserNotificationCenter. If a user has 10 repeating reminders and each is pre-scheduled for the next 6 occurrences, you hit the limit quickly. The practical solution is to schedule only the next 2–3 occurrences per reminder and reschedule on each fire event. Android has no such hard limit, but keeping pending alarm counts reasonable improves battery life.
Need this feature production-ready?
RapidDev builds customizable reminders into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.