| 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"} |
Better Notify Setup
Guide for adding typed notifications to TypeScript/JavaScript applications using Better Notify.
For code examples and syntax, see better-notify.com/docs.
Phase 1: Planning (REQUIRED before implementation)
Scan the project and ask the user structured questions before writing any code.
Step 1: Scan the project
Analyze the codebase to auto-detect:
- Framework — Look for
next.config, hono, express, fastify, or other server entry files.
- Existing notifications — Look for
nodemailer, @sendgrid, resend, postmark, ses in package.json.
- Validation library — Look for
zod, valibot, arktype in package.json (Better Notify uses Standard Schema).
- Package manager — Check for
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.
Step 2: Ask planning questions
Ask all applicable questions in a single call. Skip any you already answered from the scan.
-
Notification channels (always ask, allow multiple)
- "Which notification channels do you need?"
- Options: Email | SMS | Push notifications | Discord | Slack | Telegram | Custom channel
-
Email transport (only if Email selected)
- "Which email transport will you use?"
- Options: SMTP (Nodemailer) | Resend | Mailchimp Transactional (Mandrill) | Cloudflare Email | Mock (for development)
-
SMS transport (only if SMS selected)
- "Which SMS transport will you use?"
- Options: Twilio | Mock (for development)
-
Template engine (only if Email selected)
- "How do you want to write email templates?"
- Options: React Email | MJML | Handlebars | Inline HTML (no adapter needed)
-
Validation library (skip if detected)
- "Which validation library do you use? Better Notify supports any Standard Schema provider."
- Options: Zod | Valibot | ArkType
-
Features (always ask, allow multiple)
- "Which additional features do you need?"
- Options: Rate limiting | Idempotency (deduplication) | Suppression list | Tracing | Event logging | Dry-run mode | None
Step 3: Summarize the plan
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.
Phase 2: Implementation
Only proceed after the user confirms the plan.
Step 1: Install packages
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 |
Step 2: Define the catalog (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()...,
}),
})
Step 3: Create the client (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' }),
});
Step 4: Send notifications
import { mail } from './lib/notify-client';
const result = await mail.welcome.send({
to: 'user@example.com',
input: { name: 'Alice', verifyUrl: 'https://...' },
});
const batch = await mail.welcome.batch(
[
{ to: 'alice@example.com', input: { name: 'Alice', verifyUrl: '...' } },
{ to: 'bob@example.com', input: { name: 'Bob', verifyUrl: '...' } },
],
{ interval: 250 },
);
Step 5: Environment variables
# 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=...
Step 6: Add middleware (optional)
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() }));
Step 7: Testing with mock transports
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();
Template Adapters
React Email
import { reactEmail } from '@betternotify/react-email'
import { WelcomeEmail } from '../emails/welcome'
.template(({ input }) => reactEmail(WelcomeEmail, { name: input.name }))
Handlebars
import { handlebarsTemplate } from '@betternotify/handlebars'
.template(handlebarsTemplate('<h1>Hello {{name}}</h1>', {
text: 'Hello {{name}}',
subject: 'Welcome, {{name}}!',
}))
MJML
import { mjml } from '@betternotify/mjml'
.template(mjml(`<mjml><mj-body>...</mj-body></mjml>`))
Transport Options
Multi-transport (failover)
import { multiTransport } from '@betternotify/email';
const transport = multiTransport({
strategy: 'failover',
transports: [{ transport: primaryTransport }, { transport: fallbackTransport }],
});
Strategies: failover, round-robin, random, race, parallel, mirrored.
Resources