better-notify-setup
Interactive setup wizard for adding Better Notify to a TypeScript/JavaScript project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Interactive setup wizard for adding Better Notify to a TypeScript/JavaScript project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Quick reference for Better Notify configuration, patterns, and common gotchas
Context and API guidance for Better Notify — end-to-end typed notification infrastructure for Node.js
Seamlessly use AI SDK inside your oRPC projects without any extra overhead.
Use oRPC inside an Astro project.
Functions to encode and decode base64url strings (URL-safe variant of base64).
A plugin for oRPC to batch requests and responses to reduce overhead.
| name | better-notify/setup |
| description | Interactive setup wizard for adding Better Notify to a TypeScript/JavaScript project |
| license | MIT |
| metadata | {"author":"Ali Torki","homepage":"https://github.com/ali-master","version":"1.0.0"} |
Guide for adding typed notifications to TypeScript/JavaScript applications using Better Notify.
For code examples and syntax, see better-notify.com/docs.
Scan the project and ask the user structured questions before writing any code.
Analyze the codebase to auto-detect:
next.config, hono, express, fastify, or other server entry files.nodemailer, @sendgrid, resend, postmark, ses in package.json.zod, valibot, arktype in package.json (Better Notify uses Standard Schema).pnpm-lock.yaml, yarn.lock, bun.lockb, or package-lock.json.Use what you find to pre-fill defaults and skip questions you can already answer.
Ask all applicable questions in a single call. Skip any you already answered from the scan.
Notification channels (always ask, allow multiple)
Email transport (only if Email selected)
SMS transport (only if SMS selected)
Template engine (only if Email selected)
Validation library (skip if detected)
Features (always ask, allow multiple)
Present a concise implementation plan as a markdown checklist. Example:
## Notification Setup Plan
- **Channels:** Email, Slack
- **Email transport:** SMTP
- **Templates:** React Email
- **Validation:** Zod
- **Features:** Rate limiting, event logging
### Steps
1. Install `@betternotify/core`, `@betternotify/email`, `@betternotify/slack`
2. Install `@betternotify/smtp`, `@betternotify/react-email`
3. Create `lib/notify.ts` with channel config and catalog
4. Create `lib/notify-client.ts` with client setup
5. Create email templates under `emails/`
6. Add middleware (rate limiting, event logging)
7. Set up environment variables
Ask the user to confirm before proceeding to Phase 2.
Only proceed after the user confirms the plan.
Core (always): @betternotify/core
Channels:
| Package | When |
|---|---|
@betternotify/email | Email channel |
@betternotify/sms | SMS channel |
@betternotify/push | Push notifications |
@betternotify/discord | Discord webhooks |
@betternotify/slack | Slack messages |
@betternotify/telegram | Telegram bot |
Transports:
| Package | When |
|---|---|
@betternotify/smtp | SMTP email (Nodemailer) |
@betternotify/resend | Resend email |
@betternotify/mailchimp | Mailchimp Transactional |
@betternotify/cloudflare-email | Cloudflare Email |
@betternotify/twilio | Twilio SMS |
Templates:
| Package | When |
|---|---|
@betternotify/react-email | React Email templates |
@betternotify/mjml | MJML templates |
@betternotify/handlebars | Handlebars templates |
lib/notify.ts)import { createNotify } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { z } from 'zod';
export const ch = emailChannel({
defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
});
const rpc = createNotify({ channels: { email: ch } });
export const catalog = rpc.catalog({
welcome: rpc
.email()
.input(z.object({ name: z.string(), verifyUrl: z.string().url() }))
.subject(({ input }) => `Welcome, ${input.name}!`)
.template({
render: async ({ input }) => ({
html: `<p>Welcome, ${input.name}!</p>`,
text: `Welcome, ${input.name}!`,
}),
}),
});
Multi-channel example:
import { createNotify } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { slackChannel } from '@betternotify/slack';
import { z } from 'zod';
const rpc = createNotify({
channels: {
email: emailChannel({ defaults: { from: 'noreply@example.com' } }),
slack: slackChannel(),
},
});
export const catalog = rpc.catalog({
welcome: rpc
.email()
.input(z.object({ name: z.string() }))
.subject(({ input }) => `Welcome, ${input.name}!`)
.template({ render: async ({ input }) => ({ html: `...`, text: `...` }) }),
alert: rpc
.slack()
.input(z.object({ message: z.string() }))
.text(({ input }) => input.message),
});
Sub-catalogs (nested routes):
export const catalog = rpc.catalog({
transactional: rpc.catalog({
welcome: rpc.email()...,
reset: rpc.email()...,
}),
marketing: rpc.catalog({
newsletter: rpc.email()...,
}),
})
// Routes: transactional.welcome, transactional.reset, marketing.newsletter
lib/notify-client.ts)import { createClient, consoleLogger } from '@betternotify/core';
import { smtpTransport } from '@betternotify/smtp';
import { catalog, ch } from './notify';
export const mail = createClient({
catalog,
transportsByChannel: {
email: smtpTransport({
host: process.env.SMTP_HOST!,
port: Number(process.env.SMTP_PORT),
auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
}),
},
logger: consoleLogger({ level: 'info' }),
});
import { mail } from './lib/notify-client';
const result = await mail.welcome.send({
to: 'user@example.com',
input: { name: 'Alice', verifyUrl: 'https://...' },
});
// Batch sending
const batch = await mail.welcome.batch(
[
{ to: 'alice@example.com', input: { name: 'Alice', verifyUrl: '...' } },
{ to: 'bob@example.com', input: { name: 'Bob', verifyUrl: '...' } },
],
{ interval: 250 },
);
# SMTP
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-user
SMTP_PASS=your-pass
# Resend (alternative)
RESEND_API_KEY=re_...
# Slack
SLACK_TOKEN=xoxb-...
SLACK_DEFAULT_CHANNEL=C0123456789
# Twilio
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=...
TWILIO_FROM_NUMBER=+1234567890
# Discord
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
# Telegram
TELEGRAM_BOT_TOKEN=...
import { createNotify } from '@betternotify/core';
import { withRateLimit, withEventLogger } from '@betternotify/core/middlewares';
import { inMemoryRateLimitStore } from '@betternotify/core/stores';
import { consoleEventSink } from '@betternotify/core/sinks';
import { ch } from './notify';
const rpc = createNotify({ channels: { email: ch } })
.use(
withRateLimit({
store: inMemoryRateLimitStore(),
key: ({ args }) => args.to,
max: 5,
window: 60_000,
}),
)
.use(withEventLogger({ sink: consoleEventSink() }));
import assert from 'node:assert/strict';
import { createClient } from '@betternotify/core';
import { mockTransport } from '@betternotify/email';
import { catalog, ch } from './notify';
const mock = mockTransport();
const mail = createClient({
catalog,
transportsByChannel: { email: mock },
});
await mail.welcome.send({
to: 'test@example.com',
input: { name: 'Test', verifyUrl: 'https://example.com/verify' },
});
assert(mock.sent.length === 1);
assert(mock.sent[0].subject === 'Welcome, Test!');
mock.reset();
import { reactEmail } from '@betternotify/react-email'
import { WelcomeEmail } from '../emails/welcome'
.template(({ input }) => reactEmail(WelcomeEmail, { name: input.name }))
import { handlebarsTemplate } from '@betternotify/handlebars'
.template(handlebarsTemplate('<h1>Hello {{name}}</h1>', {
text: 'Hello {{name}}',
subject: 'Welcome, {{name}}!',
}))
import { mjml } from '@betternotify/mjml'
.template(mjml(`<mjml><mj-body>...</mj-body></mjml>`))
import { multiTransport } from '@betternotify/email';
const transport = multiTransport({
strategy: 'failover',
transports: [{ transport: primaryTransport }, { transport: fallbackTransport }],
});
Strategies: failover, round-robin, random, race, parallel, mirrored.