# Notification Grouping & Batching

- **Pattern:** `ab-000773` (`community-social-engagement.notifications.batching-grouping`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000773
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000773)

## 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 <verb> your post.' See the notification creation path:

```ts
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_count` field).
- **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:

  ```ts
  // 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: user-experience

HTML version: https://auditbuffet.com/patterns/ab-000773
