Skip to main content

Overview

The notification system consists of five database tables, a Supabase Edge Function for dispatching reminders, Web Push support in the admin dashboard, and native push in the mobile app. This guide covers the architecture, database schema, and extensibility patterns.

Architecture

                    ┌──────────────────┐
                    │  Reminder Engine  │
                    │  (Edge Function)  │
                    └────────┬─────────┘
                             │ evaluates schedules

          ┌──────────────────────────────────┐
          │       reminder_schedules         │
          │  + reminder_schedule_day_overrides│
          └──────────────┬───────────────────┘
                         │ creates

              ┌─────────────────────┐
              │    notifications    │◄── read/manage by users
              └──────┬──────────────┘
                     │ dispatches
          ┌──────────┴──────────┐
          ▼                     ▼
  ┌───────────────┐   ┌─────────────────────┐
  │  Web Push     │   │  Mobile Push / Email │
  │  (browser SW) │   │  (Expo + Resend)     │
  └───────────────┘   └─────────────────────┘

Flow

  1. The dispatch-reminders Edge Function runs on a cron schedule
  2. It evaluates reminder_schedules + reminder_schedule_day_overrides for the current time and day
  3. For each schedule due, it checks if a recent submission exists (smart skip)
  4. If a reminder is needed, it inserts a row into notifications
  5. It also dispatches to external channels: Web Push via web_push_subscriptions, mobile push via Expo, and email via Resend (if reminder_emails_enabled is true on the user)

Database Schema

notifications

Stores all in-app notifications for users.
ColumnTypeDescription
idUUIDPrimary key
user_idUUIDReferences auth.users
auction_idUUIDOptional — scopes to an auction
typeTEXTNotification type (e.g., submission_reminder, system)
titleTEXTDisplay title
bodyTEXTOptional body text
dataJSONBArbitrary metadata (URLs, IDs, etc.)
is_readBOOLEANRead status
read_atTIMESTAMPTZWhen marked as read
created_atTIMESTAMPTZCreation timestamp
RLS policies: Users can read, update, and delete their own notifications. Service role can insert and delete any.

web_push_subscriptions

Stores Web Push API subscription objects for the admin dashboard.
ColumnTypeDescription
idUUIDPrimary key
user_idUUIDReferences auth.users
endpointTEXTPush service endpoint URL
p256dhTEXTClient public key
authTEXTAuth secret
created_atTIMESTAMPTZSubscription creation time
RLS policies: Users can manage their own subscriptions. Service role reads all for dispatch.

reminder_schedules

Defines reminder schedule configurations with a three-level hierarchy.
ColumnTypeDescription
idUUIDPrimary key
auction_idUUIDNULL = global default
location_idUUIDNULL = auction-wide
modeTEXTinterval or deadline
is_enabledBOOLEANWhether this schedule is active
start_timeTIMEStart of the reminder window (interval mode)
end_timeTIMEEnd of the reminder window (interval mode)
interval_minutesINTEGERMinutes between reminders (interval mode)
deadlinesJSONBArray of deadline times (deadline mode)
advance_reminder_minutesJSONBArray of advance warning times (deadline mode)
smart_skip_enabledBOOLEANSkip if submission already exists
timezoneTEXTIANA timezone for schedule evaluation
created_atTIMESTAMPTZCreation timestamp
updated_atTIMESTAMPTZLast update timestamp
Partial unique indexes enforce the hierarchy:
  • One global default (where auction_id IS NULL AND location_id IS NULL)
  • One per auction (where location_id IS NULL)
  • One per location

reminder_schedule_day_overrides

Per-day-of-week overrides for a reminder schedule.
ColumnTypeDescription
idUUIDPrimary key
schedule_idUUIDReferences reminder_schedules
day_of_weekINTEGER0 = Sunday through 6 = Saturday
is_enabledBOOLEANWhether reminders fire on this day
start_timeTIMEOverride start time (NULL = inherit)
end_timeTIMEOverride end time (NULL = inherit)
interval_minutesINTEGEROverride interval (NULL = inherit)
deadlinesJSONBOverride deadlines (NULL = inherit)
advance_reminder_minutesJSONBOverride advance times (NULL = inherit)
Unique constraint: (schedule_id, day_of_week) — one override per day per schedule.

reminder_logs

Tracks dispatched reminders for auditing and smart-skip logic.
ColumnTypeDescription
idUUIDPrimary key
schedule_idUUIDWhich schedule triggered this
location_idUUIDThe affected location
dispatched_atTIMESTAMPTZWhen the reminder was sent
skippedBOOLEANWhether smart skip prevented delivery
recipient_countINTEGERNumber of users notified

Edge Function: dispatch-reminders

The dispatch-reminders Edge Function is the core engine that evaluates schedules and sends reminders.

Invocation

The function is designed to be called on a cron schedule (e.g., every 5 minutes) via Supabase’s pg_cron or an external scheduler.

Logic

1. Fetch all enabled reminder_schedules
2. For each schedule:
   a. Resolve the effective config (with day overrides for today)
   b. Determine if a reminder is due now based on mode:
      - Interval: check if current time is within window and interval elapsed
      - Deadline: check if any deadline advance time matches
   c. If due, check smart_skip:
      - Query recent submissions for the schedule's location(s)
      - If submission exists in current period, skip
   d. If not skipped:
      - Insert notification rows for each assigned user
      - Dispatch Web Push to web_push_subscriptions
      - Send email via Resend (if user.reminder_emails_enabled)
      - Send mobile push via Expo push API
   e. Log to reminder_logs

Environment Variables

VariableDescription
SUPABASE_URLSupabase project URL
SUPABASE_SERVICE_ROLE_KEYService role key for DB access
RESEND_API_KEYResend API key for email delivery
VAPID_PUBLIC_KEYVAPID public key for Web Push
VAPID_PRIVATE_KEYVAPID private key for Web Push

VAPID Key Configuration

Web Push requires VAPID (Voluntary Application Server Identification) keys for authentication between the server and push services.

Generating VAPID Keys

Generate a VAPID key pair using the web-push npm package:
npx web-push generate-vapid-keys
This outputs a public and private key. Store them as environment variables:
  • NEXT_PUBLIC_VAPID_PUBLIC_KEY — Used by the admin dashboard client
  • VAPID_PRIVATE_KEY — Used by the Edge Function (server-side only)
Never expose the VAPID private key to the client. Only the public key is used in the browser.

Adding New Notification Types

The notification system is extensible. To add a new notification type:
1

Define the Type

Choose a type string (e.g., inspection_complete) and document it
2

Insert Notifications

From your server action or Edge Function, insert rows into the notifications table with the new type:
await serviceClient.from('notifications').insert({
  user_id: userId,
  auction_id: auctionId,
  type: 'inspection_complete',
  title: 'Inspection Completed',
  body: `Vehicle ${vin} passed inspection`,
  data: { inspectionId, vin },
});
3

Update UI (Optional)

Add an icon mapping for the new type in the notification components:
  • Admin: NotificationItem.tsx icon switch
  • Mobile: NotificationsScreen.tsx icon mapping
4

Add Push Dispatch (Optional)

If the new type should send push notifications or emails, add dispatch logic to the relevant server action or create a new Edge Function

RLS Policies Summary

TableSELECTINSERTUPDATEDELETE
notificationsOwn rowsService roleOwn rows (mark read)Service role (verified ownership)
web_push_subscriptionsOwn rowsOwn rowsOwn rowsOwn rows
reminder_schedulesMembers see auction schedules; authenticated see globalSuper admin (global) / manager+ (auction)Same as insertSame as insert
reminder_schedule_day_overridesSame as parent scheduleSame as parent scheduleSame as parent scheduleSame as parent schedule
reminder_logsMembers see auction logsService roleNoneNone

Testing Locally

Testing Notifications

  1. Insert a notification directly via SQL or the Supabase dashboard:
    INSERT INTO notifications (user_id, type, title, body)
    VALUES ('your-user-id', 'system', 'Test Notification', 'This is a test');
    
  2. Open the admin dashboard — the notification bell should show a badge
  3. Click the bell to see the notification in the panel

Testing Web Push

  1. Set NEXT_PUBLIC_VAPID_PUBLIC_KEY in your admin .env.local
  2. Enable browser notifications in the notification panel
  3. Accept the browser permission prompt
  4. Send a test push via the Edge Function or directly via the web-push library

Testing Reminders

  1. Create a reminder schedule via the admin UI or SQL
  2. Invoke the dispatch-reminders Edge Function manually:
    supabase functions invoke dispatch-reminders --no-verify-jwt
    
  3. Check the notifications table and reminder_logs for results