This FRD ships @dmwd-io/push as an optional package that re-exports web-push as the platform standard for Web Push notifications. The value is consistency and drift prevention, not a custom abstraction — per ADR-014, we adopt the community library directly rather than building our own interface on top of it.
Introduction
Overview
Per ADR-014 (Open Source First), this package designates web-push as the platform standard for browser push notifications. @dmwd-io/push is a thin re-export of web-push with documented platform conventions: use VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT as the canonical env var names across all services. No custom PushProvider interface is built — the web-push library’s own API is the interface.
Goals
Designate web-push as the platform standard for Web Push (VAPID-based browser push).
Re-export web-push through @dmwd-io/push so all services import from a single platform-owned entry point.
Document VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT as the canonical platform env var names.
Prevent drift by giving teams one place to update if the underlying library ever changes.
Model push subscriptions as a typed, serializable data structure for persistence.
Define a failure-handling strategy with typed error categories (expired token, invalid payload, rate-limited, service unavailable).
Ship a mock adapter for testing that records dispatched notifications and simulates failure scenarios.
Non-Goals
Building a custom PushProvider interface or adapter layer — the library’s own API is the interface (per ADR-014).
Building production FCM or APNs (mobile) adapters (browser Web Push via web-push is in scope per ADR-027’s self-hosted preference).
Building a notification preferences UI or user-facing settings page.
Managing push notification content scheduling or batching at the infrastructure level.
Handling in-app notification rendering (that is a UI component concern).
Scope
In Scope
Area
Description
Re-export package
@dmwd-io/push re-exports all of web-push; no custom interface layer
Env var conventions
Document VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT as platform-standard names
Subscription model
Typed structure for device tokens, platform identifiers, and subscription metadata
Notification payload
Typed structure for title, body, data payload, badge count, and action URLs
Delivery result
Discriminated union covering delivered, failed, expired, rate-limited
Failure handling
Typed error categories with recommended caller behavior per category
Mock adapter
In-memory provider for testing with configurable failure simulation
Unit tests
Full contract conformance and mock behavior coverage
Documentation
Storybook MDX docs with usage examples
Out of Scope
Area
Reason
Custom PushProvider interface
ADR-014: community library API is the interface
FCM / APNs adapters
Vendor-specific; shipped separately
Service worker registration
Client-side concern outside library scope
Notification scheduling / queuing
Application infrastructure concern
User preference management
UI and persistence layer concern
Rich media attachments
Deferred to a future iteration
Users and Pain Points
User Groups
User
Description
Needs
Application developers
Engineers adding push notification features
A stable interface to code against without vendor coupling
QA engineers
Testers verifying notification-dependent flows
A mock that lets them assert on dispatched notifications
Platform engineers
Engineers building production adapters
A clear contract and conformance tests
Pain Points
User
Pain Point
Impact
Application developers
Each service has its own push SDK integration with different error handling
VAPID keys (required for Web Push) are stored in 1Password per ADR-072, not in the database.
Proposed Solution
Summary
Per ADR-014, @dmwd-io/push is a thin re-export of web-push. No custom interface is built — the library’s API is the interface. The package’s value is a single platform-owned import path and documented env var conventions.
// All web-push exports are available through the platform package
VAPID private key (stored in 1Password per ADR-072)
VAPID_SUBJECT
VAPID subject (mailto: or URL identifying the sender)
Key Capabilities
@dmwd-io/push re-exports all of web-push from a single platform entry point.
Documented env var names (VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT) used consistently across all services.
PushSubscription type modeling device token, platform, and metadata for persistence.
PushNotification type for payload with title, body, data, badge, and action URL.
PushDeliveryResult discriminated union with delivered, failed, expired, rate-limited statuses.
MockPushProvider with configurable failure simulation and assertion helpers.
User Experience
Not applicable (library, no UI).
Developer Experience
Developers import from @dmwd-io/push using the web-push API directly. VAPID initialization reads from the documented env vars. In tests, MockPushProvider records all dispatched notifications and can be configured to simulate specific failure modes (expired tokens, rate limits).
Requirements
ID
Requirement
Priority
Notes
FR-001
The package must re-export all of web-push from @dmwd-io/push
Must
ADR-014: library API is the interface
FR-002
The package must export a typed PushSubscription model
Must
Token, platform, metadata
FR-003
The package must export a PushDeliveryResult discriminated union
Must
Covers all outcome states
FR-004
The package must ship a MockPushProvider adapter
Must
For testing
FR-005
The package must document recommended caller behavior for each failure category
Must
Part of the contract
FR-006
The mock adapter should support configurable failure simulation
Should
For testing error paths
Priority Definitions
Priority
Meaning
Must
Required for this feature to ship.
Should
Important, but can be deferred if needed.
Could
Nice to have. Not required for initial release.
Functional Requirements
ID
Requirement
User Benefit
Priority
FUNC-001
subscribe(subscription) stores a subscription and returns a confirmation result
Uniform subscription management
Must
FUNC-002
unsubscribe(token) removes a subscription by device token
Clean token lifecycle
Must
FUNC-003
send(token, notification) dispatches a notification to a single device and returns PushDeliveryResult
Uniform send interface
Must
FUNC-004
sendBatch(messages) dispatches to multiple tokens and returns ordered results
Bulk notification support
Should
FUNC-005
PushDeliveryResult includes status, token, timestamp, and optional error with errorCategory
Callers can route error handling by category
Must
FUNC-006
When result status is expired, the caller should remove or refresh the subscription
Prevents stale token accumulation
Must
FUNC-007
MockPushProvider.getDispatched() returns all sent notifications
Test assertions
Must
FUNC-008
MockPushProvider.simulateFailure(token, status) configures a token to return a specific failure
Testing error handling paths
Should
Non-Functional Requirements
ID
Requirement
Category
Priority
NFR-001
web-push is the only runtime dependency
Maintainability
Must
NFR-002
All public types exported from the package entry point
Maintainability
Must
NFR-003
Mock adapter instances must not share mutable state
Testing
Must
NFR-004
The library must not persist or log device tokens by default
MockPushProvider.getDispatched() returns all sent notifications
FUNC-007
AC-005
MockPushProvider.simulateFailure() causes specified tokens to return configured failure status
FUNC-008
AC-006
All public types re-exported from package index
NFR-002
AC-007
Unit tests pass covering subscribe, unsubscribe, send, batch send, and all failure categories
FR-004
AC-008
Storybook MDX docs render without errors
DOC-001
AC-009
Package readme documents VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT as canonical env var names
FR-001
LLM Handoff Instructions
Expected LLM Behavior
Follow the requirements and acceptance criteria in this document.
Do not expand scope beyond the In Scope section.
Respect the Out of Scope section.
Ask for clarification only when a requirement cannot be safely interpreted.
Prefer existing design system patterns over inventing new ones.
Use discriminated unions for result types, not string enums.
The mock adapter must be a class, not a factory function.
Document recommended caller behavior for each PushDeliveryResult status in code comments.
Do NOT build a custom PushProvider interface — per ADR-014, the library’s own API is the interface.
Implementation Steps
Create packages/push/ directory if it does not exist.
Add package.json with web-push as a peer dependency and @dmwd-io/push as the package name.
Create src/index.ts with export * from 'web-push' plus the platform-specific types (PushSubscription, PushNotification, PushDeliveryResult) and MockPushProvider.
Document env var names (VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT) in the package README and in JSDoc on any initialization helpers.
Run pnpm typecheck and confirm no type errors before declaring complete.
LLM Should Not
Invent undocumented product behavior.
Connect to real push services.
Build a custom PushProvider interface wrapping web-push.
Add new dependencies without justification.
Change unrelated components.
Include real device tokens in test fixtures.
Implement scheduling or queue logic inside the provider contract.
Decision Log
Date
Decision
Reason
Owner
2026-05-26
Discriminated union for delivery results
Type narrowing for safe error handling
David Holmes
2026-05-26
Include topics and userId in subscription model
Support both topic-based and user-based subscription patterns
David Holmes
2026-05-26
Advisory expired-token handling (not enforced)
Library should not manage subscription storage; that is the consumer’s responsibility
David Holmes
2026-06-02
Reframed per ADR-014 (Open Source First): adopt web-push as thin re-export rather than building a custom provider interface. Library API is the interface.
ADR-014 mandates community library adoption over custom abstractions when the library covers the use case
David Holmes
Document History
Date
Author
Change
2026-05-26
David Holmes
Initial draft
2026-06-02
David Holmes
Reframed per ADR-014: ship as thin re-export of web-push, drop custom interface.