How to Add a Notification Center to an AI-Built App
A notification center is a bell icon and dropdown feed of read and unread events stored in your database, distinct from push notifications and email. This guide covers the data model, read state handling, and the polling vs. realtime delivery tradeoff.
How to Add a Notification Center to an AI-Built App
A notification center is a bell icon in your app's header that opens a panel of events tied to a user's account: a comment on their post, a task assigned to them, an invoice about to renew. It lives inside your own database and your own UI, which is what separates it from push notifications (OS-level alerts delivered outside your app) and email (asynchronous messages that live in someone's inbox). Building a notification center comes down to three decisions: a notifications table that stores each event, a read and unread state on every row, and a way for the client to learn new rows exist, either by polling on an interval or by receiving them over a realtime connection.
In-app notifications vs. push notifications and email
These three channels get lumped together but they usually need separate systems. A notification center is a feed inside your product: users open the bell icon, scroll through what happened, and mark things read. It only reaches people already inside your app. Push notifications need a device token, a push service like APNs or FCM, and a permission prompt; how to add push notifications to an AI-built app covers that flow in more depth. Email is asynchronous and lives outside your product entirely, in someone's inbox. A mature app typically writes every event to the notifications table first, then decides case by case whether it also deserves a push or an email. If you haven't built the underlying app yet, how to build an app with AI is the better starting point; this guide assumes the app and its database already exist and focuses on this one feature.
The data model
A notification center needs one table. Keep it flat rather than trying to model every possible event type up front; the columns below cover most apps.
Column | Type | Purpose |
|---|---|---|
id | uuid or bigint, primary key | Unique identifier for the notification row |
user_id | uuid or bigint, foreign key | The recipient the notification belongs to |
actor_id | uuid, nullable | Who or what triggered it: another user, or a system job |
type | text or enum | The kind of event: comment, mention, invoice_due, assignment |
payload | jsonb | Event-specific data the UI needs to render the message and route a click |
read_at | timestamp, nullable | Null means unread; set to a timestamp when the user reads it |
created_at | timestamp | When the event happened; used for sorting and pruning |
The payload column is what keeps this flexible without a schema migration every time you add a new event type. Store only what the UI needs to render the message and route a click, such as a post id, an amount, or a target URL. If you're building with an AI app builder like Swarmz, describe the notifications table and the bell-icon UI in your prompt and iterate on the read-state logic; the shape above is a reasonable starting point for most tools to generate correctly on the first pass.
Notification types worth planning for
Most apps settle on five or six types. Resist creating a new type for every event; group similar ones and let the payload carry the specifics.
Activity: comments, mentions, reactions on something the user owns
Assignment: a task, ticket, or lead assigned to the user
Billing: an invoice is due, a payment failed, a plan is about to renew
System: a security alert, a password change, a login from a new device
Digest: a batched summary, one row instead of ten separate ones
A generic activity type whose payload carries an actor, an action, and a target covers most social and collaboration events without adding a new type every sprint.
Polling vs. realtime delivery
Once notifications are being written to the table, the client needs a way to find out about them. There are two approaches, and most apps only need the first one.
Approach | How it works | Best for |
|---|---|---|
Polling | Client requests unread count and rows on an interval, typically every 15-60 seconds | Most apps; simplest to build, no extra infrastructure |
Realtime (websockets/SSE) | Server pushes new rows to the client the moment they're inserted | Chat-like products, high-frequency events, teams already running a realtime layer |
Polling means the client asks the server every 15 to 60 seconds for the unread count and, when the panel is open, the latest rows. It's a simple query against an index on user_id and read_at, and it works with any backend and no extra infrastructure. For a notification center where a few seconds of delay goes unnoticed, polling is the right default.
Realtime delivery pushes new rows to the client the moment they're inserted, using websockets, server-sent events, or a managed layer like Postgres change subscriptions or a hosted pub/sub service. It matters most for products where a delayed unread badge breaks the experience, which is closer to real-time chat territory than a typical notification feed. It also means a persistent connection to manage, reconnect logic, and one more moving part to monitor. Some teams split the difference: poll for the full panel, but add a lightweight realtime subscription just for the unread badge count, which keeps most of the traffic simple while making the one number people glance at most feel live.
Marking notifications read and unread
The read_at column does the work. A notification is unread when read_at is null and read once it's set to a timestamp. The unread badge is a single indexed count query, filtered to the current user's id where read_at is null. Mark a notification read when the user clicks it, and offer a 'mark all as read' action that runs one bulk update rather than a request per row.
Keep the state binary if you can. A separate dismissed or archived flag, letting users hide something without marking it read, is worth adding only if users actually ask for it. Update the UI optimistically when someone marks a notification read: clear the unread dot immediately and reconcile only if the request fails.
Clearing out old notifications
Notifications pile up fast, especially in anything social or collaborative. Don't delete them the moment they're read; people expect to scroll back through history for at least a while. A reasonable pattern is to keep everything visible with pagination, then run a scheduled job that hard-deletes rows past a retention window, commonly somewhere between 90 days and a year, to keep the table small and the queries fast. If you need a permanent record for compliance or support reasons, keep that in a separate audit log rather than relying on the notifications table; you'll want to prune one and never touch the other.
Where this fits into retention
A notification center is a retention lever as much as a UX feature. It's one of the few places you can remind a lapsed user that something is waiting for them without leaving your product. Track opens and click-through on notifications the same way you'd track any other event in the app, which is easier if adding analytics to an AI-built app is already wired up. A bell icon nobody clicks is also a symptom worth investigating alongside reducing churn on an AI subscription product, since an ignored notification feed is often an early sign that a user has already checked out.
Frequently asked questions
Do I need websockets to build a notification center?
No. Polling an indexed query every 15 to 60 seconds is enough for the vast majority of apps and is far simpler to build and operate. Reach for websockets or a managed realtime layer only when notifications need to appear instantly, which matters most for chat-adjacent features.
What's the difference between a notification center and push notifications?
A notification center is an in-app feed stored in your database and viewed inside your product. Push notifications are OS-level alerts delivered outside your app, even when it's closed, and require a separate integration with a push service and device permissions. Many apps have both, feeding off the same underlying events.
Should read and unread notifications live in separate tables?
No. Use a single nullable read_at column on one table rather than separate tables or a boolean flag. Null means unread, a timestamp means read, and it also tells you when the user saw it, which a plain boolean can't.
How do I show an unread count badge?
Run a count query filtered to the current user's id where read_at is null, indexed on user_id and read_at. Cache or debounce it on the client if you're polling frequently, since the badge doesn't need to update more often than the panel itself.
How long should notifications be kept before deleting them?
There's no fixed rule, but 90 days to a year is common for a table that's purely functional. Prune with a scheduled job rather than on read, and keep any data you need for compliance or auditing in a separate table that isn't subject to the same cleanup.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


