@mikstack/notifications
v0.3.0
Published
Code-first notification infrastructure for mikstack projects. Factory function, plugin-based channels, Drizzle table mapping, type-safe notification definitions.
Readme
@mikstack/notifications
Code-first notification infrastructure for mikstack projects. Factory function, plugin-based channels, Drizzle table mapping, type-safe notification definitions.
Install
bun add @mikstack/notificationsPeer dependency: drizzle-orm (>=0.38.0)
Quick Start
1. Define your schema (copy to your schema.ts)
export const notificationDelivery = pgTable("notification_delivery", {
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text("user_id").notNull().references(() => user.id),
type: text("type").notNull(),
channel: text("channel").notNull(),
status: text("status", { enum: ["pending", "sent", "delivered", "failed"] }).notNull().default("pending"),
content: jsonb("content"),
error: text("error"),
retryOf: text("retry_of"),
retriesLeft: integer("retries_left").notNull().default(0),
recipientEmail: text("recipient_email"),
externalId: text("external_id"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
export const inAppNotification = pgTable("in_app_notification", { ... });
export const notificationPreference = pgTable("notification_preference", { ... });2. Define notifications
import { defineNotification } from "@mikstack/notifications";
export const notifications = {
"magic-link": defineNotification({
key: "magic-link",
critical: true,
channels: {
email: (data: { url: string }) => magicLinkEmail(data.url),
},
}),
"welcome": defineNotification({
key: "welcome",
channels: {
"in-app": (data: { userName: string }) => ({
title: `Welcome, ${data.userName}!`,
body: "Get started by creating your first note.",
}),
},
}),
} as const;3. Create instance
import { createNotifications, emailChannel, inAppChannel } from "@mikstack/notifications";
const notif = createNotifications({
database: { db, schema, provider: "pg" },
channels: [
emailChannel({
sendEmail: async ({ to, subject, html, text }) => { /* your SMTP logic */ },
}),
inAppChannel(),
],
notifications,
});4. Send notifications
await notif.send({
type: "welcome",
userId: user.id,
data: { userName: user.name },
});API
| Method | Purpose |
|---|---|
| notif.send({ type, userId, data }) | Send notification across channels |
| notif.list({ userId, limit?, unreadOnly? }) | List in-app notifications |
| notif.markRead({ userId, notificationIds? }) | Mark as read |
| notif.getPreferences(userId) | Get user preferences |
| notif.updatePreferences(userId, prefs) | Update preferences |
Client SDK
import { createNotificationClient } from "@mikstack/notifications/client";
const client = createNotificationClient({ baseUrl: "/api/notifications" });
await client.markRead(["notif-id-1"]);
await client.markAllRead();
const prefs = await client.getPreferences();
await client.updatePreferences({ "welcome": { "in-app": false } });Features
- Type-safe:
send()autocompletes notification types and infers data shapes - Email retries: Exponential backoff (default 3 attempts), each attempt tracked as its own delivery row
- Preference hierarchy: Per-type + per-channel > per-type + wildcard > wildcard + per-channel > defaults
- Critical notifications:
critical: truebypasses user preferences (e.g., auth emails) - In-app via Zero:
inAppNotificationtable syncs to clients via Zero - Schema ownership: Tables are copy-pasted into your project (like better-auth), fully customizable
