# `PhoenixKit.Notifications`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.164/lib/phoenix_kit/notifications/notifications.ex#L1)

Per-user notifications driven by `PhoenixKit.Activity`.

When an activity is logged with a `target_uuid` that differs from the
`actor_uuid`, a row is inserted into `phoenix_kit_notifications` for the
target user. The user sees it in the bell dropdown (`count_unread/1`,
`recent_for_user/2`) and in the inbox at `/notifications` (`list_for_user/2`).
Each row carries its own `seen_at` and `dismissed_at` — the same activity
can be "seen but not dismissed" for one user and "unseen" for another.

The whole feature is gated by the global `notifications_enabled` setting
(default `"true"`); when `"false"`, `maybe_create_from_activity/1` is a no-op.

Registered as a core toggleable module (`use PhoenixKit.Module`) so it
appears on the admin Modules page and contributes the `/admin/notifications`
overview tab. The module enable/disable flips the same
`notifications_enabled` kill-switch `enabled?/0` reads.

# `admin_stats`

Aggregate counts for the admin overview page: total notifications,
`unread` (neither seen nor dismissed), and `dismissed`. A single
`count(...) FILTER (WHERE ...)` query — one table scan, not three.
Rescues to zeros so the page never crashes on a query hiccup.

# `count_unread`

Counts undismissed, unseen notifications for a user. Drives the badge.

# `create`

Create a **standalone** notification — one not tied to an activity
(V126). Use for app-driven notices that don't originate from the
activity log (e.g. "your export is ready").

`attrs` keys:
  * `:recipient_uuid` (required) — who receives it
  * `:text` / `:icon` / `:link` — convenience, folded into `metadata`
    as `notification_text` / `notification_icon` / `notification_link`
    (the keys `Render` reads)
  * `:metadata` — raw metadata map (merged under the convenience keys)
  * `:type` — optional notification type key (e.g. `"account"`,
    `"posts"`, or a module-contributed type). When given, the send is
    filtered through the recipient's per-type preference
    (`Prefs.user_wants_type?/2`, fail-open).
  * `:action` — optional action string (e.g. `"post.commented"`). When
    given, filtered through `Prefs.user_wants?/2` (which maps the
    action to a type). Use `:type` OR `:action`, not both.

    Notifications.create(%{
      recipient_uuid: user.uuid,
      text: "Your export is ready.",
      icon: "hero-arrow-down-tray",
      link: "/exports/123"
    })

Honors the global `notifications_enabled` kill-switch. With neither
`:type` nor `:action`, it's an unconditional app-driven send (no
preference filtering). Returns `{:ok, %Notification{}}`,
`{:ok, :skipped}` (disabled or filtered out by prefs), or
`{:error, changeset}`. Broadcasts `{:notification_created, n}` on success.

# `create_many`

Create a standalone notification for **many** recipients in one call —
the multi-recipient counterpart to `create/1`. `recipient_uuids` is a
list; `attrs` is the same shape as `create/1` minus `:recipient_uuid`
(it's supplied per recipient).

The recipient list is the caller's responsibility (e.g. the followers
of an author) — this is the generic fan-out primitive, not an audience
resolver. Duplicate uuids are de-duped. Each recipient is filtered
independently through `:type` / `:action` prefs when given, so muted
users are skipped. Honors the kill-switch once up front.

    Notifications.create_many(follower_uuids, %{
      type: "posts",
      text: "Alice published a new post.",
      link: "/posts/#{post.id}"
    })

Returns `{:ok, created_count}` (notifications actually inserted, i.e.
excluding disabled / pref-skipped) or `{:ok, :skipped}` when
notifications are globally disabled.

# `dismiss`

Dismisses a single notification. Idempotent.

# `dismiss_all`

Bulk-dismisses all undismissed notifications. Returns `{count, nil}`.

# `enabled?`

Is the notifications feature enabled? Default `true`.

# `get_notification`

Fetches one notification scoped to the recipient. Returns `nil` if missing.

# `list_for_user`

Returns `{notifications, total_count}` for the given user, newest first.

Options:
  * `:page` (default 1) / `:per_page` (default 25)
  * `:status` — `:unread` (seen_at nil) | `:all` (default)
  * `:include_dismissed` — include dismissed rows (default `false`)

# `mark_all_seen`

Bulk-marks all unseen notifications as seen. Returns `{count, nil}`.

# `mark_seen`

Marks a single notification as seen. Idempotent — already-seen rows return
`{:ok, notification}` unchanged.

# `maybe_create_from_activity`

Inserts a notification for the activity's target user, if the rules allow it.

Returns one of:
  * `{:ok, %Notification{}}` — row created; broadcast on the per-user topic
  * `{:ok, :skipped}` — filtered out (no target, self-action, feature disabled)
  * `{:error, changeset}` — insert failed (logged, never raised)

# `prune`

Deletes notifications whose underlying activity is older than `days`.

# `recent_for_user`

Returns the N most-recent undismissed notifications for a user.

Drives the bell dropdown. Activity (and actor) are preloaded.

# `retention_days`

Retention period in days. Falls back to activity retention if unset.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
