On this page
  1. Install
  2. Define and compose notifications
  3. Configuration reference
  4. Persistence and testing
  5. Outbound-only composition
  6. Caller-keyed durable enqueue
  7. API entry points and requirements
  8. Peer dependencies

@playstack/notifications

Typed user-directed notifications across queued mail, push delivery, and a persisted in-app inbox.

Free. MIT licensed. Check preview availability before installing. See package access.

@playstack/notifications defines what a user-directed notification means, validates its input, applies channel preferences, and routes work to queued mail, push delivery, or a persisted in-app inbox. It does not own broadcasts, audiences, templates, provider clients, application routes, or SMS.

Install

sh
npm install @playstack/notifications @playstack/delivery @playstack/events

Define and compose notifications

ts
import { createNotifications, defineNotifications } from '@playstack/notifications'

const registry = defineNotifications({
  'account.invitation.accepted': {
    input: invitationAcceptedSchema,
    category: 'transactional',
    channels: ['mail', 'push', 'inApp'],
    digestible: false,
    render: {
      mail: ({ input, locale }) => ({
        payload: { template: 'invite', input, locale },
      }),
      push: ({ input }) => ({
        payload: { title: 'Invitation accepted', body: input.summary },
      }),
      inApp: ({ input }) => ({
        title: 'Invitation accepted',
        body: input.summary,
        href: '/members',
      }),
    },
  },
})

const notifications = createNotifications({
  registry,
  preferences,
  locales,
  recipients,
  queue,
  delivery,
  inbox,
  events,
  clock,
  ids,
})

enqueue() validates input and creates endpoint-free, per-channel jobs. process() revalidates the job, preference, locale, and current recipient endpoint before rendering or dispatching it. Push recipients resolve to opaque device-token IDs and fan out through @playstack/delivery/push; provider credentials are loaded only by the delivery worker.

Configuration reference

NotificationsOptions propertyRequiredPurpose
registryYesTyped definitions, Standard Schema input validators, channels, categories, and renderers.
preferencesYesResolves policy, quiet-hours, and marketing preferences per channel.
localesYesResolves the recipient locale at enqueue and execution time.
recipientsYesResolves the current delivery subject and mail endpoint.
queueYesEnqueues endpoint-free notification jobs.
deliveryYesHands rendered mail payloads to @playstack/delivery.
inboxFor in-app channelsTransactional inbox persistence; omit for mail/push-only registries.
admissionsFor keyed enqueueDurable admission and queue-handoff replay persistence.
eventsYesEmits queued, delivered, failed, read, and archived lifecycle events.
clock, idsYesApplication-owned time and identifiers.
readRetentionMsNoRead-row retention; defaults to 90 days.
safeOriginsNoExplicit HTTPS origins allowed in in-app notification links.

Transactional notifications bypass only marketing-preference decisions. They still honor policy and quiet-hours decisions and always pass through delivery suppression.

Persistence and testing

Outbound-only composition

Mail/push-only registries can omit inbox. An in-app definition without an inbox fails configuration, and inbox read/mutation/retention methods reject without an adapter. Do not supply a dummy store to compose mail-only notifications.

Caller-keyed durable enqueue

ts
import { PrismaNotificationAdmissionPersistence } from '@playstack/notifications/prisma'

const admissions = new PrismaNotificationAdmissionPersistence(prisma, {
  namespace: 'product:production',
})
const notifications = createNotifications({ ...options, admissions })
await notifications.enqueue(name, input, recipient, context, {
  idempotencyKey: 'invitation:opaque-id:accepted',
})

This composition fragment requires the optional notifications.admissions.prisma artifact and a root Prisma client. Persist the logical key with the producer's domain event and reuse it on every retry. Unkeyed calls remain new notifications; a key without admission persistence rejects before queue I/O. Conflicting input or recipient under the same key rejects instead of changing the existing intent.

Admissions retain the original notification ID, validated portable JSON input, recipient, context and initial channel plan. Keep secrets, rendered content and raw addresses out of jobs/admissions; storage is not encrypted. Worker-time recipient and preference authority is rechecked.

A lost queue acknowledgment can repeat the same channel key. The queue/outbox must retain that key or a tombstone for the full replay lifetime, including after consumption; a short deduplication TTL or remove-on-complete is insufficient. Confirmed handoffs are recorded before queued-event emission, so that event can be retried without enqueueing again. Observational events remain at-least-once.

The host owns durable producer replay, downstream deduplication, retention and erasure. Admissions have no automatic expiry/deletion policy and do not join an arbitrary domain transaction. Queue acceptance is not provider delivery; pair with the durable delivery worker for provider attempts. Neither layer promises exactly-once delivery.

@playstack/notifications/prisma exports PrismaNotificationInboxPersistence, and the notifications.prisma artifact installs the inbox schema fragment. Applications own migration execution. @playstack/notifications/testing provides deterministic in-memory queue and inbox implementations.

In-app persistence and its delivered event share one transaction. Queue and observational event failures cannot make another notification channel fail.

API entry points and requirements

Reference snapshot: @playstack/notifications@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

Public entry pointDeclaration file
@playstack/notifications./dist/index.d.ts
@playstack/notifications/errors./dist/errors.d.ts
@playstack/notifications/prisma./dist/prisma.d.ts
@playstack/notifications/testing./dist/testing.d.ts
@playstack/notifications/package.jsonNo TypeScript declaration (asset or metadata export).

Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.

Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

Go

Playstack Pro tag
OriginsPricingBlogNewsletterChangelogStatusRoadmap
ContributorsCommunityIn Use ShowcaseCase StudiesPartnersSponsors
FAQsSupportContact

© 2026 Playstack. All rights reserved.

With OSS
Terms of ServicePrivacy PolicyCookie PolicyImprint

By

Commune Software