This morning we wired up booking emails with Resend — now let's give your users CONTROL over them. Evening prompt = notification preferences page + in-app bell with unread count, and a Twilio SMS stub for later.
Paste this into Claude Code / Cursor (agent mode):
Build the ClipBook notifications system on top of this morning's email setup (Day 12A). Do it in three parts:
1. NOTIFICATION PREFERENCES PAGE (/settings/notifications):
- Logged-in user sees a list of event types: Booking confirmed, Booking reminder (24h before), Review request, Cancellation notice
- Each event has an email ON/OFF toggle
- Add table notification_preferences: id, user_id, event_type, email_enabled (boolean default true)
- RLS: user can read/write only their own rows. Seed all four event types for the user on first visit
- Toggle updates should write through Supabase and reflect instantly (optimistic UI)
- Wire it into the Day 12A email sending logic: before sending any email, check the recipient's preference for that event type — skip if email_enabled is false
2. SMS REMINDER STUB (Twilio-ready interface):
- Create lib/sms.ts with a sendSms(to: string, body: string) function
- It should read TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER from env — if any are missing, log "[SMS stub] would send to {to}: {body}" and return gracefully (no crash)
- Comment the top of the file: "Drop your Twilio keys in .env.local to go live"
- Add an sms_enabled toggle per event on the preferences page, stored the same way as email prefs (default false, since Twilio isn't connected yet)
3. IN-APP NOTIFICATION BELL:
- Create table notifications: id, user_id, type, title, body, read (boolean), created_at
- RLS: user reads/updates only their own rows
- Bell icon in the top nav with a red unread count badge (Supabase realtime subscription on the notifications table)
- Clicking opens a dropdown: newest first, unread bolded, "Mark all as read" button
- Create a notification row whenever a booking is confirmed, cancelled, or a review request goes out (same triggers as the Day 12A emails)
- Empty state: "No notifications yet — book a haircut to see this in action"
Acceptance criteria:
- Turn off "Booking confirmed" email in preferences → confirm a booking → no email sent, but in-app notification still appears
- Bell badge count updates live without page refresh (open two tabs, mark read in one)
- Leaving Twilio keys empty never throws an error anywhere
- npm run build passes with no TypeScript errors
What you'll get when it works 👇