# Notifications and customer feedback

URL: https://ship-and-scale.vercel.app/docs/core/notifications-feedback

Add account messages and customize feedback while respecting private recipients and shared content.

## Account inbox behavior [#account-inbox-behavior]

The dashboard bell shows the signed-in account's unread count. Opening the popover displays the five newest messages without marking them read. Selecting a message marks it read and opens `/dashboard/notifications` at that message. The full inbox paginates 20 messages at a time; Mark all as read includes older pages.

Messages belong to the account across workspaces. Owners/admins cannot read teammates' inboxes. The welcome notification is an in-app signup example, independent of welcome email delivery. Failed loads offer retry; failed updates must not claim that read state changed.

The header refreshes on opening, focus/visibility and every 60 seconds while visible. The implementation does not require a Realtime subscription or a general job queue. Customer text is rendered as escaped plain text.

## Preferences and suppression [#preferences-and-suppression]

Both email and in-app preferences default on. In-app opt-out suppresses future inserts through a database BEFORE INSERT trigger, including trusted backend inserts. Existing messages remain readable. Re-enabling does not replay suppressed messages.

An insertion producer must use `RETURNING` and accept an empty result as intentionally suppressed. It must not repeatedly insert until a row appears. Welcome/invitation emails check the separate email preference immediately before each send attempt; essential account-access emails remain available.

## Add a trusted notification producer [#add-a-trusted-notification-producer]

The table is `public.notifications` with `user_id`, `kind`, `title`, `body`, `created_at` and `read_at`. Authenticated clients have SELECT and UPDATE of `read_at` only. Do not expose a generic endpoint where a caller can select any recipient and message.

For a one-time operator-authored announcement, create a new versioned migration with your own event kind and content. This example inserts a nonconfidential account announcement once per current user and respects the suppression trigger:

```sql
insert into public.notifications(user_id, kind, title, body)
select u.id, 'notes_available', 'Workspace notes are available',
  'Open your workspace to read notes shared with your team.'
from auth.users u
where not exists (
  select 1 from public.notifications n
  where n.user_id = u.id and n.kind = 'notes_available'
)
returning id;
```

The current `kind` column accepts text of 1–80 characters; it is not an enum. Choose a stable event name within that limit. The migration ledger prevents concurrent reapplication of this file. For a recurring producer, add a durable event ID and database uniqueness rather than relying on a read-before-insert check under concurrency.

Do not include a private note body in an account notification without deciding what happens when its recipient leaves the workspace. Account inbox retention differs from current workspace membership. A generic “notes available” message avoids copying tenant-confidential content into a long-lived account record.

## Read-state changes [#read-state-changes]

Read updates must derive the account from verified authentication and filter by that account. Preserve the database grant that permits only `read_at`; clients must not change recipient, title or body. Use the existing notification helpers/actions and refresh the header summary after successful writes.

Test an account with more than one inbox page: Mark all should clear the unread count for all pages, while reading one message changes only that message. UTC dates avoid server/browser timezone disagreement in the included presentation.

## Shared feature requests [#shared-feature-requests]

The account menu exposes Feature request and Bug report. Feature requests support shared product feedback and votes. This shared feature content is intentionally different from workspace-private product records: visible feature content can be shared while voter/submitter identity remains private.

Use `docs/FEEDBACK.md`, `app/actions/feedback.ts` and the feature UI to trace current submission/search/vote rules. Keep server validation, retry IDs, limits and identity separation intact when changing labels or layout. A workspace owner does not become a global feedback moderator by owning a customer workspace.

## Private bug reports [#private-bug-reports]

Bug reports belong to the reporting account with attribution determined by the server. They are not public feature cards. Do not add report bodies or reporter email addresses to public blog/search/Markdown exports. Reports can contain private product context; collect only fields your support process needs.

The starter does not provide a complete operator moderation or customer impersonation interface for these records. Adding such an interface is a new product/security decision, not a documentation customization. Preserve account deletion and attribution rules when extending data retention.

## Customize and verify [#customize-and-verify]

Edit copy in the feedback components while preserving controlled form labels, success/error states and keyboard dialog behavior. Test double submissions, repeat votes, opt-out, failure/retry and cross-account access. Use the existing hosted transactional tests for recipients and private identity, and browser tests for popovers/forms.

The local documentation site does not create notification or feedback records. A fixture-based UI check proves presentation only. Verify the inserted announcement's `RETURNING` result on an authorized target, including an opted-out account, before publishing that migration as a product change.