Notification Grouping & Batching
Why it matters
One notification per like scales linearly with your viral posts — a post with 500 likes creates 500 bell pings, and the user stops opening the app. Grouping collapses that into 'Alex and 499 others liked your post,' which preserves the signal without the noise. Without batching, push-notification open rates crater, email digests become unreadable, and heavy users disable notifications platform-wide rather than per-type, which you cannot recover from.
Severity rationale
Medium because ungrouped notifications actively drive users to disable the channel, which is hard to reverse.
Remediation
On notification creation, upsert into an existing unread row keyed by (userId, type, targetId) — append the new actor to an actorIds array and increment similarCount instead of inserting a new row. Render the grouped form as 'FirstActor and N others your post.' See the notification creation path:
const existing = await db.notification.findFirst({ where: { type, targetId, userId, readAt: null } })
if (existing) return db.notification.update({ where: { id: existing.id }, data: { actorIds: [...new Set([...existing.actorIds, actorId])], similarCount: existing.similarCount + 1 } })
Detection
-
ID:
batching-grouping -
Severity:
medium -
What to look for: Enumerate all relevant files and Examine notification creation logic. Check whether multiple similar notifications are grouped together (e.g., "User A, User B, and 3 others liked your post" instead of 5 separate notifications). Look for aggregation queries or "latest actor" logic in the notification display component or database schema (e.g., a
similar_countfield). -
Pass criteria: At least 1 conforming pattern must exist. Similar notifications are grouped. The UI shows aggregated notifications: "5 people liked your post" instead of 5 separate like notifications. Multiple notifications from the same type/action are consolidated.
-
Fail criteria: Each action creates a separate notification; users see 10 notifications if 10 people like a single post.
-
Skip (N/A) when: Notification grouping is not a concern or platform is very small-scale.
-
Detail on fail:
"Each post like creates a separate notification — 100 likes = 100 notifications"or"No grouping logic in notification display component" -
Remediation: Implement notification grouping:
// Notification model with grouping model Notification { id String @id userId String type String // 'likes', 'follows', 'comments' targetId String // Post/User ID actorIds String[] // Array of user IDs who triggered this notification similarCount Int @default(0) lastActorAt DateTime readAt DateTime? createdAt DateTime } // Group notifications on creation async function createNotification(type: string, actorId: string, targetId: string, userId: string) { const existing = await db.notification.findFirst({ where: { type, targetId, userId, readAt: null } }) if (existing) { // Update existing grouped notification return db.notification.update({ where: { id: existing.id }, data: { actorIds: [...new Set([...existing.actorIds, actorId])], similarCount: existing.similarCount + 1, lastActorAt: new Date() } }) } else { // Create new notification return db.notification.create({ data: { type, targetId, userId, actorIds: [actorId] } }) } } // Display grouped notification function NotificationItem({ notification }) { if (notification.similarCount > 0) { return ( <p> {notification.actorIds[0]}, and {notification.similarCount} others {notification.type} your post </p> ) } return <p>{notification.actorIds[0]} {notification.type}</p> }
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-social-engagement·automated