Notifications Vllnt
Per-subject directed inbox — deliver notifications with read/unread state and fan-out as a Bijection component
git clone https://github.com/bijectionhq/notifications-vllnt.git
cd notifications-vllnt
git checkout 30678273bded3b391b72610c93eac590861b5a2eBijection 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.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- 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.
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 anotificationIdfor each. - Read state — every notification arrives unread;
markRead(idempotent) andmarkAllRead(bounded, self-rescheduling) clear it. - Inbox queries —
listpages newest-first (all or unread-only),unreadCounttotals the unread,getfetches one. Reactive in a Convex query. - Subject-bounded reads — a
list/unreadCountis keyed to one subject and never spans another's inbox. - Server-sourced time —
createdAt/readAtare stamped from the server clock; a caller can't supply a timestamp. - Typed, opaque payload —
Notifications<TPayload>types the storedpayload;payloadValidatornarrows 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.usemounts; 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
| Method | Kind | Result |
|---|---|---|
deliver(ctx, subjects, type, payload?) | mutation | { notificationIds } (subjects: one ref or an array) |
markRead(ctx, notificationId) | mutation | null |
markAllRead(ctx, subjectRef, opts?) | mutation | number (marked in the first bounded pass) |
get(ctx, notificationId) | query | NotificationView | null |
list(ctx, subjectRef, paginationOpts, opts?) | query | PaginationResult<NotificationView> (opts: { unreadOnly? }) |
unreadCount(ctx, subjectRef) | query | number |
purge(ctx, opts?) | mutation | number (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/payloadstay 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.