Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Notifications Vllnt

Per-subject directed inbox — deliver notifications with read/unread state and fan-out as a Bijection component

Bijection source
git clone https://github.com/bijectionhq/notifications-vllnt.git
cd notifications-vllnt
git checkout 30678273bded3b391b72610c93eac590861b5a2e

Bijection repository access is required. Request access.

Description

Per-subject directed inbox — deliver notifications with read/unread state and fan-out as a Bijection component

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available as a reference

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/notifications-vllnt. Based on vllnt/convex-notifications. License: MIT. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

Per-subject directed inbox — deliver notifications with read/unread state and fan-out as a Convex component

Explore the demo ↗

Upstream overview

From the upstream package documentation.

@vllnt/convex-notifications stores a notification inbox for each opaque subject reference. The application defines notification types and a typed payload, so the same component can serve different product surfaces.

deliver writes one notification per recipient, including fan-out to several recipients. list and unreadCount support inbox screens and badges through host queries. markRead and bounded markAllRead clear unread state. Retention cleanup removes eligible read notifications.

The host authenticates recipients, gates access, and chooses which events create notifications. This is an in-app data store; it does not send APNs/FCM push notifications, SMS, or email. Device delivery and channel preferences belong in your integration.

Maintained by bntvllnt at VLLNT. Find the author on GitHub and X.

Use cases

From the upstream package documentation.

  • Team tools: notify people about mentions, assignments, or shared-resource updates.
  • Mobile apps: retain account and product notices in an in-app inbox.
  • Learning platforms: display lesson, feedback, or course activity notices.
  • Games and communities: surface invitations, replies, or host-defined milestones.

How it works

From the upstream package documentation.

deliver writes one notification per recipient, including fan-out to several recipients. list and unreadCount support inbox screens and badges through host queries. markRead and bounded markAllRead clear unread state. Retention cleanup removes eligible read notifications.

The host authenticates recipients, gates access, and chooses which events create notifications. This is an in-app data store; it does not send APNs/FCM push notifications, SMS, or email. Device delivery and channel preferences belong in your integration.

Upstream package FAQ

Does @vllnt/convex-notifications handle authentication or user identity?

@vllnt/convex-notifications is auth-agnostic. The host application resolves identity and decides which subjectRef a caller may read from or deliver to. The component accepts any opaque string as a subjectRef and never inspects or stores user session data directly.

How does the component prevent cross-user inbox leakage?

Every list and unreadCount call in @vllnt/convex-notifications is keyed to a single subjectRef and cannot span another subject's inbox. Tables are sandboxed and reachable only through the component's exported functions, so host or sibling tables are never touched.

Can I store custom data with each notification?

Yes. The Notifications class is generic: new Notifications<TPayload>(components.notifications, { payloadValidator }) lets you type and validate the payload at the component boundary using a Convex validator. The stored payload stays opaque to the component itself and is returned as-is in NotificationView results.

Is this component safe to mount multiple times in the same Convex app?

@vllnt/convex-notifications is mount-safe. Each named app.use mount in convex.config.ts creates an isolated sandbox, so multiple instances do not share tables or interfere with each other.

Does the component include any React UI or frontend code?

@vllnt/convex-notifications is backend-only and has no ./react entry point. Inbox lists, unread counts, and individual notifications are fetched through standard useQuery calls against query functions you re-export from your host app using the component's list, unreadCount, and get methods.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Notifications Vllnt with Bijection.

Read: https://bijection.com/components/notifications-vllnt/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from vllnt/convex-notifications, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

@vllnt/convex-notifications

A per-subject directed inbox — notifications with read/unread state and fan-out, as a Convex component.

const inbox = new Notifications(components.notifications);
await inbox.deliver(ctx, recipients, "mention", { actor }); // one row per recipient
await inbox.list(ctx, subjectRef, paginationOpts, { unreadOnly: true }); // reactive inbox
await inbox.unreadCount(ctx, subjectRef);
await inbox.markRead(ctx, notificationId);

Features

  • Deliver + fan-out — deliver(subjectRef | subjectRefs[], type, payload?) writes one notification per recipient and mints a notificationId for each.
  • Read state — every notification arrives unread; markRead (idempotent) and markAllRead (bounded, self-rescheduling) clear it.
  • Inbox queries — list pages newest-first (all or unread-only), unreadCount totals the unread, get fetches one. Reactive in a Convex query.
  • Subject-bounded reads — a list/unreadCount is keyed to one subject and never spans another's inbox.
  • Server-sourced time — createdAt/readAt are stamped from the server clock; a caller can't supply a timestamp.
  • Typed, opaque payload — Notifications<TPayload> types the stored payload; payloadValidator narrows it at the boundary.
  • Bounded purge + cron — a daily cron sweeps read notifications past retention in batches; unread are never purged.
  • Mount-safe — correct under multiple named app.use mounts; each instance is an isolated sandbox.

Installation

pnpm add @vllnt/convex-notifications

Peer dependency: convex@^1.41.0.

Usage

// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "@vllnt/convex-notifications/convex.config";

const app = defineApp();
app.use(notifications);
export default app;
// convex/notify.ts — host owns auth; pass opaque subjectRefs in.
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { Notifications } from "@vllnt/convex-notifications";

const inbox = new Notifications<{ actor: string }>(components.notifications, {
  payloadValidator: v.object({ actor: v.string() }).parse, // narrow at the boundary
});

export const notifyMention = mutation({
  args: { recipients: v.array(v.string()), actor: v.string() },
  handler: (ctx, { recipients, actor }) =>
    inbox.deliver(ctx, recipients, "mention", { actor }),
});

export const myUnread = query({
  args: { subjectRef: v.string(), paginationOpts: v.any() },
  handler: (ctx, { subjectRef, paginationOpts }) =>
    inbox.list(ctx, subjectRef, paginationOpts, { unreadOnly: true }),
});

API Reference

MethodKindResult
deliver(ctx, subjects, type, payload?)mutation{ notificationIds } (subjects: one ref or an array)
markRead(ctx, notificationId)mutationnull
markAllRead(ctx, subjectRef, opts?)mutationnumber (marked in the first bounded pass)
get(ctx, notificationId)queryNotificationView | null
list(ctx, subjectRef, paginationOpts, opts?)queryPaginationResult<NotificationView> (opts: { unreadOnly? })
unreadCount(ctx, subjectRef)querynumber
purge(ctx, opts?)mutationnumber (read notifications removed in the first bounded pass)

Full reference: docs/API.md.

React

Backend-only — no ./react entry. An inbox, unread count, and unread list are ordinary reactive useQuery calls over the host's own re-exported list / unreadCount / get refs.

Security

  • Auth-agnostic — the host resolves identity and decides who may deliver to or read a subjectRef.
  • Tables sandboxed — reached only through the exported functions; never touches host or sibling tables.
  • Subject-bounded reads + server-sourced time; subjectRef / payload stay opaque to the component.

See docs/API.md.

Testing

pnpm test           # single run
pnpm test:coverage  # enforced 100% on covered files

Tests run against the real component runtime via convex-test (@edge-runtime/vm), not mocks.

Contributing

See CONTRIBUTING.md.

Author

Built by bntvllnt · bntvllnt.com · X @bntvllnt

Part of the @vllnt Convex component fleet — vllnt.com

If this is useful, sponsor the work.

License

MIT — see LICENSE.

Keywords