Persistent Notification Inbox
A bell badge that actually remembers what you've seen — badge reflects genuine new activity, not the same items on every reload.
The notification bell now connects to a server-side inbox. Matches, compliance overdue items, urgent staffing requests, and proposal deadlines persist between page loads. Mark individual items or everything at once — the badge stays at zero until something genuinely new happens.
What you get
Built to solve the actual problems hiring teams hit every day.
Persistent across sessions
Notifications are stored in the database. A refresh doesn't reset the badge — only new events do.
Deduplication built-in
A UNIQUE constraint on (profile, event type, resource) prevents the same event from appearing twice, even across repeated cron runs.
Mark all read in one click
One button sets read_at on every unread notification. The badge drops to zero immediately.
Read state preserved
Once you mark a notification read, re-running the cron that generated it does not reset the read state.
Role-aware visibility
Each notification is scoped to the profile that generated it. Candidates see their matches; clients see urgent reqs and proposal deadlines.
How it works
Four steps from setup to value.
Events persist on the server
When proposals enter their 7-day window, the cron upserts a notification row for each org member — the badge increments next time they open the app.
Badge reflects real unread count
GET /api/notifications returns only your unread rows. The badge is the count of rows where read_at is null.
Click the bell
The panel lists your 20 most recent notifications. Unread items have a blue dot; read items are dimmed.
Mark all read
Click "Mark all read" in the panel header. All unread rows are updated server-side; the badge drops to zero.
FAQ
Quick answers to common questions.
Why did my badge reset on every reload before?
Notifications were built entirely in-memory from live queries. There was no persistence layer, so read_at was always null, and the badge always showed the full count.
Will the same notification appear twice?
No. The UNIQUE(profile_id, type, resource_id) index means a cron re-run for the same proposal (or match, or compliance item) silently ignores the duplicate insert and leaves your existing row's read_at intact.
What events generate inbox notifications?
Currently: proposals due within 7 days (generated by the daily proposal scan). The same pattern extends to matches, compliance overdue items, and urgent staffing requests as those crons are updated.