After implementing notifications, the next idea often comes quickly: "let us add analytics."
The problem starts when analytics is reduced to one general status and that status is then used to infer user behavior.
Delivery, read, and reaction actually answer three different questions:
- did the notification reach the user;
- did the user see it;
- did the user do anything afterward.
Delivery Only Tells You Whether the Notification Was Delivered
Delivery is a transport-level signal.
It may mean that a notification was:
- successfully stored in the database;
- sent over WebSocket;
- accepted by a push provider;
- accepted by another delivery transport.
But successful delivery says nothing about whether the user actually noticed the notification.
Read Is a Separate User-Level Signal
Read is closer to user behavior, but the exact moment still needs a clear definition.
Is a notification considered read when the panel opens? When the item becomes visible? When the user clicks that specific notification?
This is a product decision that should be defined before calculating any read rate.
delivered_atread_atReaction Is a Response to the Notification Content
The third level is an action that happens after the notification is seen.
That may be:
- following a link;
- opening the entity referenced by the notification;
- approving or rejecting something;
- replying;
- another business action.
This is where analytics begins to answer whether the notification was actually useful to the user or the process.
One notification_status Field Quickly Becomes a Weak Model
sentdeliveredreadclickedactedSuch an enum assumes that every notification moves through one linear sequence.
In reality, the events may be independent. A user may reach the target page through another path without opening the notification panel, or read a notification without reacting to it.
Separate timestamps or events often model reality better than one "highest" status.
What Is Actually Worth Storing
notification_iduser_idcreated_atdelivered_atread_at optional:action_typeaction_atchannelsource_contextBasic timestamps are enough to answer many useful questions:
- how many notifications are actually delivered;
- how many are read;
- how long it takes users to read them;
- which notification types are ignored more often.
Actions after a notification are worth storing only when they have a clear product meaning.
Which Metrics Easily Become Vanity Metrics
A large number of delivered notifications says very little about system quality by itself.
Likewise, a "90% read rate" may look excellent while being almost meaningless if every notification is automatically marked as read when the panel opens.
A metric becomes a vanity metric when it:
- looks good when it grows;
- is easy to display on a dashboard;
- but does not help make a decision or improve the product.
Start With Questions, Not Metrics
Instead of asking "what else can we count?", start by defining which decision the analytics should support.
For example:
- do users notice critical notifications at all;
- which notification types are read too late;
- does a certain notification actually lead to the intended action;
- is the system generating too much noise.
Once those questions are clear, it becomes much easier to decide which events are worth storing.
Read Rate Only Makes Sense After Read Has a Clear Definition
read_rate = notifications_with_read_at / delivered_notificationsIf the meaning of "read" changes between product versions, historical data stops being directly comparable.
That means event semantics are part of the analytics contract, not just an implementation detail.
Reaction Should Not Be a Generic Universal Field
The word "reaction" sounds convenient, but different notifications have different goals.
For one notification, the useful action may be opening a message. For another, approving an operation. For a third, opening a document.
It is often better to store the specific action or connect the notification to a business event than to set a generic reacted = true.
Do Not Duplicate a Business Event Just for Notification Analytics
If the system already has a trustworthy "order_approved" or "message_replied" event, there is no need to create another copy of the same action only inside notification analytics.
When analysis requires attribution, connect the notification to the real business event instead.
This keeps analytics from becoming a second source of truth for what happened in the product.
Store Channel Only When It Changes Decisions
If the same notification can arrive through in-app, email, or push, the channel can be useful as a separate dimension.
But it is worth storing only when the team will actually compare channel effectiveness or adjust channel behavior.
If the field exists only because "we may need it one day," it quickly becomes an unused dimension.
A Practical Event Model
notification.creatednotification.deliverednotification.read business action:order.approvedmessage.replieddocument.openedThis model makes it possible to analyze notifications without turning the notification system into the center of all product analytics.
The notification system owns notification facts, while the business module owns the real user action.
What to Test
1. Delivery does not set read_at.2. Read does not require a business action.3. Repeated read events are idempotent.4. delivered_at is not rewritten on duplicate delivery.5. Business action is stored by its own domain.6. Analytics can join notification and business action when needed.7. Read-rate calculation uses delivered notifications as denominator.8. Historical events keep stable semantics.Good Notification Analytics Starts With Clear Boundaries
Delivery shows that the transport worked. Read shows that the user saw the notification. Reaction or a business action shows what happened afterward.
These signals are related, but they are not interchangeable.
Store not everything that can be counted, but the signals that help answer a concrete product question or support a decision.
The most useful notification metric is not the easiest one to count, but the one that makes the next product decision clearer.



