Notifications

Distribu surfaces business events two ways inside the dashboard:

  • In-app notifications — a per-user, per-company stream of actionable events (new orders, status changes, returns, team changes, etc.), delivered to a bell icon in the header with an unread count and dropdown. Full list at /dashboard/notifications.
  • Activity feed — a lighter, company-wide feed on the dashboard home, rendered from the audit log. Broader than notifications (includes things like CSV imports, product edits) but not actionable.

Notifications are per-recipient: when an event fans out to three staff, three rows are written — each with their own read state. Marking one read doesn't affect anyone else.

The event types

Thirteen notification types fire today:

TypeFires when…
ORDER_PLACEDA new order is placed (storefront or API).
ORDER_STATUS_CHANGEDAn order transitions between statuses.
ORDER_LOW_STOCKAn order placement pushed a product below its low-stock threshold.
CUSTOMER_REGISTEREDA customer self-registers on your storefront.
MEMBER_INVITEDA staff invite is sent.
MEMBER_JOINEDAn invited staff member accepts and joins the team.
MEMBER_REMOVEDA staff member is removed from the team.
MEMBER_ROLE_CHANGEDA staff member's role changes.
SCHEDULED_REPORT_FAILEDA scheduled report dispatch fails.
SUBSCRIPTION_PAST_DUEStripe reports a failed invoice payment.
SUBSCRIPTION_CANCELEDYour subscription is canceled.
RETURN_REQUESTEDA staff member opens a new return.
REFUND_PROCESSEDA refund is issued against a return.

Role fan-out

Not every event goes to every staff member. Who receives what depends on the event type:

Event typesOWNERADMINMEMBER
ORDER_PLACED, ORDER_STATUS_CHANGED, ORDER_LOW_STOCK, CUSTOMER_REGISTERED, RETURN_REQUESTED
REFUND_PROCESSED, MEMBER_INVITED, MEMBER_JOINED, MEMBER_REMOVED, MEMBER_ROLE_CHANGED, SCHEDULED_REPORT_FAILED, SUBSCRIPTION_PAST_DUE, SUBSCRIPTION_CANCELED

Day-to-day operational events (orders, returns, low stock, new customers) reach everyone. Sensitive / administrative events (team changes, billing, refund decisions, report failures) stay with OWNER and ADMIN.

Actor exclusion

If the person who triggered the event is a staff user, they're excluded from their own notification — no point telling you you just did a thing you did. Customer-triggered events (storefront order placement, self-register) and system-triggered events (Stripe webhooks, cron failures) don't exclude anyone; every staff member in the role set gets notified.

The bell icon

In the dashboard header, a bell icon carries a red badge with the unread count. The badge:

  • Hides entirely at 0.
  • Shows "99+" when the unread count exceeds 99.
  • Updates via polling — the client calls GET /api/notifications/count every 60 seconds.
  • Pauses polling when the tab is backgrounded (Page Visibility API). When the tab becomes visible again, one immediate refresh fires, then 60-second polling resumes.

Clicking the bell opens a dropdown with the 10 most recent notifications. Each row shows an icon, title, body, relative timestamp, and is clickable — click navigates to the event's destination (e.g., the order detail page for ORDER_PLACED) and marks the notification read optimistically.

A "Mark all as read" button sits at the footer of the dropdown, and "View all" links to the full page.

The full list

Navigate to /dashboard/notifications for a paginated list (25 per page) with All / Unread tabs. Each row has an inline Mark read action; a header-level Mark all as read button clears everything with one click.

Notification payload

Each notification row stores:

FieldTypeNotes
typeenumOne of the 13 types above.
titlestringShort headline — e.g., "New order #A1B2C3D4". Denormalised at write time so renames of referenced entities don't retroactively change notification history.
bodystringOne-line description.
hrefstring, optionalWhere clicking the notification navigates. Null for events with no destination.
metadataJSONType-specific structured data (order ID, customer name, from/to statuses, etc.).
readAttimestamp, optionalWhen the user read it. Null means unread.
createdAttimestampWhen the notification was created.

Multi-company users

Notifications are scoped to (userId, companyId). A user who's a member of two companies sees only the current session's company's notifications in the bell — switching companies switches the stream.

API

Four routes back the bell + list UI. All require an authenticated dashboard session (cookie-based) with role OWNER, ADMIN, or MEMBER.

MethodPathPurpose
GET/api/notificationsPaginated list. Query: limit (1–100, default 25), cursor, filter=unread|all. Returns { items, nextCursor, unreadCount }.
GET/api/notifications/countCheap unread-count poll. Returns { unreadCount }. This is what the bell polls every 60 s.
POST/api/notifications/[id]/readMark one read. Returns { ok, unreadCount }. 404 if the notification isn't owned by (userId, companyId).
POST/api/notifications/read-allMark all current-company notifications read. Returns { ok, markedCount }.

These are session-authenticated (not API-key). They're not documented in the REST API section because they're for the dashboard UI itself.

The activity feed

Separately from notifications, the dashboard home page shows a company-wide activity feed rendered directly from the audit log. It covers a superset of events (including CSV imports, product edits, API-key creation, etc.) but it's read-only — no unread state, no per-user scoping.

Use the activity feed when you want a quick "what's been happening"; use notifications when you want to act on something.

Email digest

Optionally, Distribu will roll up your unread notifications into a daily or weekly email so you don't have to stare at the bell all day. Off by default; each user opts in per company.

See Digest emails for cadence, timing, and configuration.


Next: Digest emails.