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.

Steve Jefferson
Steve Jefferson
Developer Advocate
24 August 20261 min read

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

Steve Jefferson
Steve Jefferson

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.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.