Skip to content

FRD: Notifications Provider

Document Summary

FieldDetails
Feature NameNotifications Provider
StatusDraft
OwnerDavid Holmes
ContributorsEngineering
Target Releasev2.0.0
Related LinksADR-051 (Provider Pattern), ADR-027 (Default Tech Stack), ADR-014 (Open Source First), src/components/widgets/notification-bell.tsx, src/components/patterns/notification-feed.tsx, src/components/ui/cards/notification-card.tsx
Last Updated2026-06-02
Open Source Librarieszod
DocumentationResend Docs (email) · PostHog Docs (in-app) · Twilio Docs (SMS) · web-push README (push)

This FRD ships @dmwd-io/notifications as an optional package that re-exports zod (interface only) as the platform standard for in-app notification delivery — coordinating with @dmwd-io/email, @dmwd-io/push, and @dmwd-io/sms for cross-channel consistency. The value is platform-wide standards and drift prevention, not a custom abstraction layer.


Introduction

Overview

The design system ships three notification UI components — notification-bell, notification-feed, and notification-card — but there is no shared data model behind them. Each consuming app defines its own notification shape, fetches from its own API, and implements read/unread tracking independently. Per ADR-014 (Open Source First), this FRD designates zod (interface only) as the community library for notification schema validation, re-exports it through @dmwd-io/notifications, and documents the platform env var conventions. No custom NotificationsProvider interface is built — the library’s own API is the interface.

Goals

  • Designate zod (interface only) as the platform standard for notification schema validation.
  • Re-export zod (interface only) via @dmwd-io/notifications so all apps pull from one canonical source.
  • Document platform env var conventions for notification channel configuration (see channel-specific env vars).
  • Provide a canonical Notification Zod schema used by all notification UI components.
  • Integrate with the existing notification-bell (unread count badge) and notification-feed (scrollable list) widgets.

Non-Goals

  • Building push notification infrastructure (FCM, APNs, Web Push).
  • Implementing email or SMS notification channels — this provider is for in-app notifications only.
  • Building new notification UI components beyond integrating with existing widgets.
  • Implementing notification routing or fan-out logic (backend concern).
  • Notification template rendering or content management.
  • Building a custom provider interface or adapter layer — the library’s own API is the interface (per ADR-014).

Scope

In Scope

AreaDescription
Re-export package@dmwd-io/notifications re-exporting zod (interface only) as the platform standard
Notification schemaNotification Zod schema with id, type, title, body, read status, timestamp, action URL, and metadata
Preference modelNotificationPreferences Zod schema for per-category opt-in/opt-out and delivery channel settings
Env var conventionsPlatform-standard env var names for notification channel configuration
Widget integrationDocumentation and examples wiring notification-bell and notification-feed to the schema

Out of Scope

AreaReason
Custom provider interfacePer ADR-014, the library’s API is the interface
Custom adapter factoryPer ADR-014, apps use the library directly
Vendor adapter implementationsSeparate packages per ADR-051
Push notifications (FCM, APNs, Web Push)Infrastructure concern, not a library contract
Email/SMS notification channelsCovered by @dmwd-io/email; out-of-band channels are separate
Notification content authoring/templatesBackend content management concern
Notification grouping/threadingFuture extension; initial scope is flat notification list

Users and Pain Points

User Groups

UserDescriptionNeeds
App developersEngineers building notification featuresA single typed schema for notification data with platform-standard validation
QA engineersTesters validating notification flowsDeterministic mock data for read, unread, and dismissed states
Design system maintainersLibrary contributorsA data contract that the existing notification widgets can consume directly

Pain Points

UserPain PointImpact
App developersEach app defines its own notification shape; notification-card props vary per appWidgets cannot be used without app-specific data transformation
App developersNo shared schema means notification widgets show static data in StorybookCannot demo real-time notification arrival or read/unread transitions
App developersUnread count logic is reimplemented in every appInconsistent badge behavior across apps

Definitions

TermDefinition
NotificationAn in-app message delivered to a user, with a type, title, body, and optional action URL
UnreadA notification that has not been explicitly marked as read by the user
DismissedA notification the user has removed from their feed (soft delete)
Notification preferenceA per-category opt-in/opt-out setting controlling which notifications a user receives
SubscriptionA real-time listener that receives new notifications as they arrive

Current State

Existing Behavior

notification-bell.tsx renders an icon with an unread count badge. It accepts unreadCount as a prop. notification-feed.tsx renders a scrollable list of notification-card components. Each card accepts title, body, timestamp, and isRead props. There is no shared data model — each app maps its own API response to these props.

Current Limitations

  • No shared Notification TypeScript type — each app defines its own.
  • No read/unread state management contract — apps track read state in their own stores.
  • No real-time delivery abstraction — apps wire their own WebSocket or SSE connections.
  • No preference management — users cannot opt out of notification categories.
  • notification-bell unread count must be computed and passed in by the app.

Existing Workarounds

  • Apps fetch notifications from their own APIs and transform the response before passing to widgets.
  • Storybook stories hard-code 2-3 notification objects with static read/unread state.
  • Unread count is computed in the app and passed as a number prop to the bell widget.

Database Schema

The notifications provider requires a notifications table in PostgreSQL:

-- notifications table
CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- e.g. "mention", "assignment", "billing.invoice_paid"
title TEXT NOT NULL,
body TEXT,
action_url TEXT,
read_at TIMESTAMPTZ, -- NULL = unread
dismissed_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_notifications_user_unread
ON notifications (user_id, read_at)
WHERE read_at IS NULL;
CREATE INDEX idx_notifications_tenant_user
ON notifications (tenant_id, user_id, created_at DESC);

NotificationPreferences table

CREATE TABLE notification_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
category TEXT NOT NULL, -- e.g. "billing", "mentions", "system"
in_app BOOLEAN NOT NULL DEFAULT TRUE,
email BOOLEAN NOT NULL DEFAULT TRUE,
push BOOLEAN NOT NULL DEFAULT FALSE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, category)
);

Migration tool: Atlas (preferred) or Drizzle Kit per ADR-027 §4.


Proposed Solution

Summary

Per ADR-014, @dmwd-io/notifications is a thin re-export package. It re-exports zod (interface only) as the platform standard and documents the env var conventions for notification channel configuration. No custom interface is built — the library’s API is the interface.

import { NotificationSchema, NotificationPreferencesSchema } from '@dmwd-io/notifications';

Environment variable conventions follow the channel-specific env var names documented in the package README. No custom adapter or factory function is provided.

Key Capabilities

  • Zod schemas for Notification and NotificationPreferences validated at runtime.
  • Re-export of zod (interface only) so all apps share one version and one import path.
  • Platform env var documentation for in-app, email, push, and SMS channel configuration.
  • TypeScript types inferred from Zod schemas — no separate type declarations needed.

User Experience

End users see consistent notification behavior across apps — same badge counting, same read/unread semantics, same feed interaction patterns — because all apps validate against the same schema.

Developer Experience

Developers import from @dmwd-io/notifications and get the canonical Zod schema. The notification-bell reads unreadCount from their own data layer. The notification-feed calls their own API and validates the response against the shared schema. No factory function or context provider is needed.


Requirements

IDRequirementPriorityNotes
FR-001Re-export zod (interface only) from @dmwd-io/notificationsMustPer ADR-014
FR-002Export a canonical NotificationSchema Zod schema with id, type, title, body, read, timestamp, actionUrl, and metadataMust-
FR-003Export NotificationPreferencesSchema Zod schema with per-category settingsShould-
FR-004Export TypeScript types inferred from Zod schemasMust-
FR-005Document platform env var conventions in package READMEMust-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001NotificationSchema validates id, type, title, body, read, timestamp, actionUrl, metadata, categoryApps share one validated shapeMust
FUNC-002NotificationPreferencesSchema validates per-category in-app, email, push boolean settingsApps share one preferences shapeShould
FUNC-003Inferred Notification TypeScript type exported alongside schemaNo manual type duplicationMust
FUNC-004Inferred NotificationPreferences TypeScript type exported alongside schemaNo manual type duplicationShould
FUNC-005Package README documents all platform env var names for notification channelsTeams configure channels consistentlyMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Package has zero runtime dependencies beyond zod (interface only)PerformanceMust
NFR-002Zero vendor SDK imports in the re-export packageMaintainabilityMust
NFR-003Notification timestamps are ISO 8601 strings, not Date objects, for serialization safetyCompatibilityMust
NFR-004Package is tree-shakeable — unused schemas add no bundle weightPerformanceMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
NotificationSchemaZod schema{ id, type, title, body, read, timestamp, actionUrl, metadata, category }Yes
NotificationTypeScript typeInferred from NotificationSchemaYes
NotificationPreferencesSchemaZod schema{ categories: Record<string, { inApp: boolean }> }Yes
NotificationPreferencesTypeScript typeInferred from NotificationPreferencesSchemaYes

Example Usage

import { NotificationSchema, type Notification } from '@dmwd-io/notifications';
// Validate an API response at runtime
const notification = NotificationSchema.parse(apiResponse);
// Use the inferred type in component props
function NotificationCard({ notification }: { notification: Notification }) {
return <div>{notification.title}</div>;
}

API Notes

  • Notification.type is a free string (apps define their own categories like info, warning, billing, security).
  • Notification.metadata is Record<string, unknown> for app-specific data (e.g. linking to a specific resource).
  • Timestamps are validated as ISO 8601 strings by the schema.

Accessibility Requirements

IDRequirementNotes
A11Y-001Package is a data layer — accessibility requirements apply to consuming componentsBell and feed widgets handle their own a11y
A11Y-002Notification.title and Notification.body must be plain text suitable for screen reader announcementNo HTML in notification content
A11Y-003unreadCount must be computable from schema-validated data so the bell can set aria-label="N unread notifications"Enables accessible badge

Checklist

  • Keyboard support is defined. (N/A — no UI)
  • Focus behavior is defined. (N/A — no UI)
  • Screen reader behavior is defined. (N/A — no UI)
  • Color contrast requirements are met. (N/A — no UI)
  • Reduced motion behavior is considered. (N/A — no UI)
  • Semantic HTML expectations are documented. (N/A — no UI)
  • ARIA usage is defined only where needed. (N/A — no UI)

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page explaining the re-export pattern and notification schemaStorybookMust
DOC-002Inline JSDoc on every exported schema and typeSource codeMust
DOC-003Example: wiring notification-bell and notification-feed using schema-validated dataStorybookMust
DOC-004Example: validating a real API response against NotificationSchemaStorybookMust
DOC-005Example: notification preferences UI using NotificationPreferencesSchemaStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
ADR-051 provider patternArchitectureEngineeringReadyDefines contract structure
ADR-014 Open Source FirstArchitectureEngineeringReadyMandates thin re-export over custom interface
notification-bell.tsxComponentDesign systemReadyExisting widget to integrate with
notification-feed.tsxComponentDesign systemReadyExisting widget to integrate with
notification-card.tsxComponentDesign systemReadyCard component used in the feed

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Zod schema is generic to cover all appsSome apps may need richer notification structuresmetadata field provides an escape hatch for app-specific data
Preference model is simple (per-category boolean)Cannot express complex rules (e.g. time-of-day quiet hours)Start simple; extend with schedule-based preferences in a future version
No built-in mock providerTeams must build their own test fixturesDocument a recommended fixture pattern in the package README
Re-export approach means zod version is pinned to the packageApps using a different zod version may see peer dep conflictsDeclare zod as a peer dependency with a broad version range

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should notifications support priority levels (urgent, normal, low)?David HolmesOpen
Q-002Should the schema include a delete flag for permanent removal, or is dismissed sufficient?David HolmesOpen
Q-003Should Notification.actionUrl support deep links (app routes) or only full URLs?David HolmesOpen
Q-004Should the schema support batched markRead (array of IDs) as a type helper?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001NotificationSchema is exported from @dmwd-io/notificationsFR-002
AC-002Inferred Notification TypeScript type is exported alongside the schemaFR-004
AC-003NotificationPreferencesSchema is exported from @dmwd-io/notificationsFR-003
AC-004Package has zero runtime dependencies beyond zod (interface only)NFR-001
AC-005Platform env var names are documented in the package READMEFR-005
AC-006Storybook example validates a sample API response against NotificationSchemaDOC-004
AC-007No vendor SDK is imported in the re-export packageNFR-002
AC-008pnpm typecheck passes with no errors

LLM Handoff Instructions

Expected LLM Behavior

  • Create a packages/notifications/ directory (or confirm it exists).
  • Add a package.json declaring zod (interface only) as a peer dependency.
  • Create src/index.ts that re-exports the relevant Zod schemas and inferred types.
  • Document the platform env var names for notification channel configuration in README.md.
  • Run pnpm typecheck and confirm it passes before marking the task complete.

LLM Should Not

  • Build a custom NotificationsProvider interface or adapter factory — per ADR-014 the library’s API is the interface.
  • Import any vendor SDK in the re-export package.
  • Implement a production vendor adapter.
  • Modify existing notification UI components — only define a data schema compatible with them.
  • Add runtime dependencies beyond zod (interface only).
  • Implement push notification infrastructure (FCM, APNs, Web Push).

Decision Log

DateDecisionReasonOwner
2026-05-26Use ISO 8601 strings for timestamps instead of Date objectsSerialization safety across JSON boundariesDavid Holmes
2026-05-26Notification.type is a free string, not a closed unionApps define their own notification categories; the provider should not constrain themDavid Holmes
2026-05-26dismiss is a soft delete, not permanent removalUsers may want to recover dismissed notifications; permanent delete is a future extensionDavid Holmes
2026-06-02Reframed per ADR-014 (Open Source First): adopt zod (interface only) as thin re-export rather than building a custom provider interface. Library API is the interface.ADR-014 mandates open source first; no custom abstraction is needed when the library’s types and schemas serve the same purposeDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft
2026-06-02David HolmesReframed per ADR-014: ship as thin re-export of zod (interface only), drop custom interface.