Ultimate

Notify

One declaration, many channels: fan-out, a preference gate, a digest window, a delivery ledger and an in-app inbox. Package @ultimat3/notify (tier 4) — the full reference is packages/notify/README.md.

notifier() is a job factory: it returns a JobHandle, so a notification inherits retry, the dead-letter path, cancellation, x jobs show and its manifest row — the same shape backfill() and llm() take (The eight primitives). It is not a ninth primitive.

import { inAppChannel, mailChannel, notifier, t } from '@ultimat3/notify';

export const commentPosted = notifier({
  name: 'post.commented',
  input: t.object({ postId: t.uuid, orgId: t.uuid, author: t.string }),
  tenant: (params) => params.orgId,          // required, as on job()
  key: (params) => `comment:${params.postId}`, // the queue's dedupe key AND the ledger's event key
  recipients: ({ input, ctx }) => posts.subscribers(input.postId, ctx.signal),
  deliver: [
    { channel: inAppChannel() },               // immediately
    {
      channel: mailChannel({ mailer }),
      wait: '10m',                             // then re-read the condition: a mute in minute 3 wins
      unless: ({ event }) => event.params.author === 'system',
      digest: { window: '1h', group: (event) => event.params.postId },
    },
  ],
});

await commentPosted.enqueue({ params: { postId, orgId, author } });

The stores

notifyStores: { ledger, inbox, digest, preferences } in export const runtime = { … } (apps/<app>/runtime.ts), installed once by the boot, whole-object replacement (a test: installNotifyStores from @ultimat3/notify/adapter):

StoreDefaultPostgres
ledger — the delivery claimin-memory (one process is genuinely deduped)postgresDeliveryLedger({ executor, windowMs }) — windowMs never shorter than your idempotency window
preferences — the gateallow allyours: the gate ships, what it reads never does — your taxonomy, your quiet hours. One method, allowsMany({ recipients, notifier, channel, event, ctx }), asked once per chunk of at most NOTIFY_CHUNK_SIZE (500) recipients per channel: answer the ids that may be reached. An omitted id is suppressed on that channel only
inboxnone — X_NOTIFY_STORE_MISSINGpostgresInboxStore({ executor })
digestnone — X_NOTIFY_STORE_MISSINGpostgresDigestStore({ executor }) — one window per slot across replicas; memoryDigestStore() for one process

The hourly x.purge job sweeps the Postgres ledger, inbox and digest windows (a closed window a week old, by default) — and, in the same pass, x_job_events, the stored bus step.waitForEvent reads. The inbox is swept only when your app.config.ts sets notify.inboxReadRetentionMs / notify.inboxUnreadRetentionMs — when an unread message disappears is your decision (Configuration).

Channels, the inbox, and at-least-once

  • deliveryChannel(name, fn) delivers per recipient; bulkChannel(name, fn) makes one call for the whole audience (a Slack post, a webhook) and cannot take a digest (X_NOTIFY_DIGEST_UNSUPPORTED). inAppChannel(), mailChannel({ mailer }) and pushChannel({ pusher, message }) ship; mailer and pusher are structural, so Mail and @ultimat3/pwa plug in without an import — Push.
  • requireInbox(name) answers list, unreadCount, markSeen, markRead. seenAt and readAt are two facts, and the unread count is derived, never stored.
  • A recipient id named twice in the audience is one recipient: the list is deduplicated by id, first entry kept, before any channel runs.
  • A replay does not send twice: the step checkpoint per channel and chunk of 500 recipients (deliver:<channel>:chunk-<n>), and the ledger's atomic claim on (notifier, key, channel, recipient) taken before each send — a chunk that failed part-way re-sends nobody it reached. 10k recipients on two channels is 40 preference reads and 40 delivery steps. A digest's append and drain stay one step per recipient; its send is one step per chunk.
  • Two attempts at once: one sends. The other raises X_NOTIFY_DELIVERY_HELD and retries past the holder's reclaimAfterMs (5 minutes by default), so a crashed attempt's recipient is still sent to and a late settle moves nothing.
  • Entries fire in wait order; if / unless run after the wait, on the attempt that delivers.

Codes: X_NOTIFY_CHANNELS_EMPTY, X_NOTIFY_CHANNEL_DUPLICATE, X_NOTIFY_DIGEST_UNSUPPORTED, X_NOTIFY_FANOUT_TOO_WIDE (default cap 500 recipients), X_NOTIFY_STORE_MISSING, X_NOTIFY_DELIVERY_FAILED, and native push's X_NOTIFY_PUSH_DEVICE_GONE, X_NOTIFY_PUSH_PROVIDER_REFUSED, X_NOTIFY_PUSH_MESSAGE_INVALID — Error codes.

SMS

Rule: an SMS is a channel over the smsTransport port; no provider ships (axiom 8). As of 2026-11-09.

import { smsChannel } from '@ultimat3/notify';

export const loginCodeSms = smsChannel<{ code: string }>({
  message: ({ event }) => ({ key: 'sms.login_code', params: { code: event.params.code } }),
});
FactDetail
the portsmsTransport in apps/<app>/runtime.ts — SmsTransport on @ultimat3/notify/adapter: send({ to, body, idempotencyKey, signal }) → { transport, id, to, idempotencyKey }
defaultmemory in development and test; elsewhere X_NOTIFY_SMS_TRANSPORT_MISSING (terminal)
prove yoursbehavesLike(smsTransportConformance, () => yours); a transport calls assertSmsNumber(to) first, and the suite checks it does
the bodythe catalog key, rendered in recipient.locale, else the app's fallback
the numbernumberOf(recipient), default recipient.to, E.164. Not E.164 → X_NOTIFY_SMS_NUMBER_INVALID (terminal; the number is never in the cause). None → skipped, logged notify.address_missing
retriessmsIdempotencyKey(notifier, key, channel, recipient) is the same on every attempt

Push

pushChannel delivers one notification to every device the recipient subscribed, each in the locale that browser subscribed with. As of 26.1.0. The transport is @ultimat3/pwa's webPush(): RFC 8291 encryption and an RFC 8292 (VAPID, ES256) token, on WebCrypto — no dependency.

import type { Recipient } from '@ultimat3/notify';
import { notifier, pushChannel, t } from '@ultimat3/notify';
import { webPush } from '@ultimat3/pwa';

// The app's own read: who follows a post. A notifier never touches the database itself.
declare const posts: {
  subscribers(postId: string, signal: AbortSignal): Promise<readonly Recipient[]>;
};

export const commentPushed = notifier({
  name: 'post.commented.push',
  input: t.object({ postId: t.uuid, commenter: t.string }),
  tenant: 'none',
  key: (params) => `comment-push:${params.postId}`,
  // The audience — every device of each of them is reached. Omitted, the audience is empty and
  // nothing is sent unless every enqueue names its own `recipients`.
  recipients: ({ input, ctx }) => posts.subscribers(input.postId, ctx.signal),
  deliver: [
    {
      channel: pushChannel<{ postId: string; commenter: string }>({
        pusher: webPush(),
        // Catalog KEYS, never text: rendered per subscription, in its own locale.
        message: ({ event }) => ({
          titleKey: 'push.comment.title',
          bodyKey: 'push.comment.body',
          params: { who: event.params.commenter },
          url: `/posts/${event.params.postId}`, // a path on this app; anything else opens the root
          tag: `post:${event.params.postId}`, // set it: a retry re-sends, a tag makes it a replacement
        }),
      }),
    },
  ],
});
PieceWhere
configpwa: { push: true, vapid: { subject: 'mailto:ops@example.com' } } (Configuration)
key pairx vapid create seals ULTIMATE_VAPID_PUBLIC_KEY + ULTIMATE_VAPID_PRIVATE_KEY; a local process with neither signs with a published development pair, a deployed boot refuses it (X_PWA_VAPID_KEY_MISSING)
subscriptionsx_push_subscriptions, a framework table the boot applies; the boot installs the store
subscribe / unsubscribeexport const subscribePush = pushSubscribe({ permission: 'push:subscribe' }) and pushUnsubscribe(…) in an actions module — real actions, on the one authz path, stored against ctx.actor.id; an agent never subscribes
the buttonsubscribeToPush({ save: client.subscribePush }) from @ultimat3/pwa/client, on a click: asks permission, subscribes with the key in <meta name="x-push-key">, saves the page's locale and the device's zone

What each push-service answer does:

AnswerOutcome
201delivered
404 / 410, or an expired subscriptionthe subscription is deleted; nothing is retried
429 / 5xx / no connectionX_PWA_PUSH_FAILED — thrown after every other device was tried, so the delivery fails as X_NOTIFY_DELIVERY_FAILED and the notifier job retries on its own backoff
400 / 413X_PWA_PUSH_REJECTED — logged and counted, never retried
401 / 403X_PWA_PUSH_REJECTED, and the subscription is deleted: it was made with a VAPID key this server no longer signs with. subscribeToPush replaces such a browser's subscription the next time it asks

A retry re-sends to the devices that already got it; the tag makes that a replacement on the device rather than a second notification. renotify without a tag is dropped with a warning — the spec makes that pair show nothing at all.

Native push — iOS and Android

nativePush({ devices, apns, fcm }) is the pusher for native apps, As of 2026-11-06: APNs over HTTP/2 with token auth (an ES256 JWT over the team's .p8 key) and FCM HTTP v1 (a service-account assertion exchanged for an OAuth token) — WebCrypto and core's outbound() with a deadline, no SDK. Both credentials are cached and re-minted before they expire.

import {
  memoryPushDeviceStore,
  nativePush,
  notifier,
  pushChannel,
  pushRegister,
  pushUnregister,
  t,
} from '@ultimat3/notify';

// Production: `postgresPushDeviceStore({ executor: dbExecutor() })` — `x_push_devices`, a
// framework table the boot applies. ONE store, handed to the actions and the pusher alike.
const devices = memoryPushDeviceStore();

export const registerDevice = pushRegister({
  permission: 'push:subscribe',
  platforms: ['ios', 'android'],
  devices,
});
export const unregisterDevice = pushUnregister({
  permission: 'push:subscribe',
  platforms: ['ios', 'android'],
  devices,
});

const phones = nativePush({
  devices,
  apns: { teamId: 'TEAM123456', keyId: 'KEY1234567', topic: 'com.example.app' },
  fcm: { projectId: 'example-app', clientEmail: 'push@example-app.iam.gserviceaccount.com' },
});

export const approvalRequested = notifier({
  name: 'approval.requested',
  input: t.object({ approvalId: t.uuid }),
  tenant: 'none',
  key: (params) => `approval:${params.approvalId}`,
  deliver: [
    {
      // Data-only: the id and nothing else. Nothing reaches a lock screen or the provider.
      channel: pushChannel<{ approvalId: string }>({
        pusher: phones,
        data: ({ event }) => ({ approvalId: event.params.approvalId }),
      }),
    },
  ],
});
PieceWhere
private keysx secrets set ULTIMATE_APNS_PRIVATE_KEY (the .p8) · x secrets set ULTIMATE_FCM_PRIVATE_KEY (the service account's private_key). Never an option; read when a token is minted, boxed in a Secret, never logged. Missing: X_ENV_MISSING; unreadable: X_CONFIG_INVALID
devicesx_push_devices, keyed (platform, token) — a phone a second person signs in on is theirs now
registerregisterDevice on every launch; previousToken when the OS rotated it. unregisterDevice on sign-out. Stored against ctx.actor.id; an agent never registers
data-onlypushChannel({ pusher, data }) — APNs background, priority 5, content-available: 1; FCM with no notification. ≤ 8 string keys, ≤ 256 characters, no reserved key: else X_NOTIFY_PUSH_MESSAGE_INVALID. webPush() cannot take it — a type error
visiblepushChannel({ pusher, message }), the same NotifyPushMessage as the web: rendered per device locale
Provider answerOutcome
2xxdelivered
APNs 410 · FCM UNREGISTEREDX_NOTIFY_PUSH_DEVICE_GONE — the device row is deleted, the delivery settles
other 4xxX_NOTIFY_PUSH_PROVIDER_REFUSED — logged, next device tried; fails the delivery when no device took it. 401/403 also drops the cached credential
429 · 5xx · timeoutrethrown after every other device was tried: X_NOTIFY_DELIVERY_FAILED, the job retries

A bare FCM 404 is never "gone" (a wrong projectId answers it too), nor is APNs BadDeviceToken (a sandbox token sent to production): reading either as dead would delete every device.

Edit this page on GitHubThis page as markdown