Back to skills
SKILL.md
Setup
ASecurityInteractive setup wizard for adding Better Notify to a TypeScript/JavaScript project
- 8 stars
- 0 votes
- 0 copies
- 3 views
- Added September 12, 2026
Works with
Security analysis
100/100npx -y skills add ali-master/skills --skill setup --agent claude-codeAre you the author of Setup?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ali-master-setup)---
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](https://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.
1. **Notification channels** (always ask, allow multiple)
- "Which notification channels do you need?"
- Options: Email | SMS | Push notifications | Discord | Slack | Telegram | Custom channel
2. **Email transport** (only if Email selected)
- "Which email transport will you use?"
- Options: SMTP (Nodemailer) | Resend | Mailchimp Transactional (Mandrill) | Cloudflare Email | Mock (for development)
3. **SMS transport** (only if SMS selected)
- "Which SMS transport will you use?"
- Options: Twilio | Mock (for development)
4. **Template engine** (only if Email selected)
- "How do you want to write email templates?"
- Options: React Email | MJML | Handlebars | Inline HTML (no adapter needed)
5. **Validation library** (skip if detected)
- "Which validation library do you use? Better Notify supports any Standard Schema provider."
- Options: Zod | Valibot | ArkType
6. **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:
```markdown
## 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`)
```typescript
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:**
```typescript
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):**
```typescript
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
```
### Step 3: Create the client (`lib/notify-client.ts`)
```typescript
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
```typescript
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 },
);
```
### Step 5: Environment variables
```env
# 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)
```typescript
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
```typescript
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
```typescript
import { reactEmail } from '@betternotify/react-email'
import { WelcomeEmail } from '../emails/welcome'
.template(({ input }) => reactEmail(WelcomeEmail, { name: input.name }))
```
### Handlebars
```typescript
import { handlebarsTemplate } from '@betternotify/handlebars'
.template(handlebarsTemplate('<h1>Hello {{name}}</h1>', {
text: 'Hello {{name}}',
subject: 'Welcome, {{name}}!',
}))
```
### MJML
```typescript
import { mjml } from '@betternotify/mjml'
.template(mjml(`<mjml><mj-body>...</mj-body></mjml>`))
```
---
## Transport Options
### Multi-transport (failover)
```typescript
import { multiTransport } from '@betternotify/email';
const transport = multiTransport({
strategy: 'failover',
transports: [{ transport: primaryTransport }, { transport: fallbackTransport }],
});
```
Strategies: `failover`, `round-robin`, `random`, `race`, `parallel`, `mirrored`.
---
## Resources
- [Docs](https://better-notify.com/docs)
- [GitHub](https://github.com/better-notify/better-notify)
- [Examples](https://github.com/better-notify/better-notify/tree/main/examples)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.