Installation
Install Better-Notify and its dependencies
Better-Notify is ESM-only and requires Node >= 22 or Bun.
Scaffold with the CLI
The CLI scaffolds a project with channels, routes, a typed client, and a working transport — ready to send.
npx create-better-notify@latestThe CLI walks you through channel selection (email, SMS, push), transport provider, and schema library. When it finishes, you get a project with this structure:
src/
notifications/
channel.ts # channel definition with defaults
routes.ts # routes grouped into sub-catalogs
client.ts # typed client wired to your transport
index.ts # entry point — sends a test notification
.env # provider credentialsRun the generated project to send your first notification:
npx tsx src/index.tsThe Your First Email walkthrough explains what each file does.
Manual setup
To install manually, follow the steps below.
Confirm your runtime
Use Node 22 or newer, or Bun.
For new projects, set ESM in package.json:
{
"type": "module"
}Install the packages
npm install @betternotify/core @betternotify/email @betternotify/smtp zod@betternotify/core exports createNotify() and createClient(). @betternotify/email adds the email channel. @betternotify/smtp adds the SMTP transport. zod defines route input schemas used throughout these docs.
Add SMTP credentials
Create a .env file with your SMTP credentials:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=mailer@example.com
SMTP_PASS=your-passwordThe variable names are up to your app. Your smtpTransport(...) call must read valid credentials at runtime.
Define a channel and send
Create a minimal setup to verify that the transport connects and delivers:
import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { smtpTransport } from '@betternotify/smtp';
import { z } from 'zod';
const email = emailChannel({
defaults: { from: { name: 'My App', email: process.env.SMTP_USER! } },
});
const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
welcome: rpc
.email()
.input(z.object({ name: z.string() }))
.subject(({ input }) => `Welcome, ${input.name}!`)
.template({
render: async ({ input }) => ({
text: `Welcome, ${input.name}!`,
html: `<h1>Welcome, ${input.name}!</h1>`,
}),
}),
});
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!,
},
}),
},
});
const result = await mail.welcome.send({
to: 'you@example.com',
input: { name: 'Ada Lovelace' },
});
console.log('sent!', result.messageId);A delivered email confirms your setup.
Confirm your runtime
Use Node 22 or newer, or Bun.
For new projects, set ESM in package.json:
{
"type": "module"
}Install the packages
npm install @betternotify/core @betternotify/telegram zod@betternotify/core exports createNotify() and createClient(). @betternotify/telegram adds the Telegram channel and Bot API transport. zod defines route input schemas used throughout these docs.
Add Telegram credentials
Create a bot with @BotFather, then add the token to .env:
TELEGRAM_BOT_TOKEN=123456789:your-bot-token
TELEGRAM_CHAT_ID=-1001234567890Use a user, group, or channel chat ID for TELEGRAM_CHAT_ID. Your bot must be able to post to that chat.
Define a channel and send
Create a minimal setup to verify that the bot can deliver:
import { createNotify, createClient } from '@betternotify/core';
import { telegramChannel, telegramTransport } from '@betternotify/telegram';
import { z } from 'zod';
const telegram = telegramChannel();
const rpc = createNotify({ channels: { telegram } });
const catalog = rpc.catalog({
deployAlert: rpc
.telegram()
.input(z.object({ service: z.string(), version: z.string(), status: z.string() }))
.body(({ input }) => `<b>${input.service}</b> deployed ${input.version}\nStatus: ${input.status}`)
.parseMode('HTML'),
});
const notify = createClient({
catalog,
transportsByChannel: {
telegram: telegramTransport({ token: process.env.TELEGRAM_BOT_TOKEN! }),
},
});
const result = await notify.deployAlert.send({
to: process.env.TELEGRAM_CHAT_ID!,
input: { service: 'api', version: 'v2.4.0', status: 'healthy' },
});
console.log('sent!', result.messageId);A delivered Telegram message confirms your setup.
Confirm your runtime
Use Node 22 or newer, or Bun.
For new projects, set ESM in package.json:
{
"type": "module"
}Install the packages
npm install @betternotify/core @betternotify/discord zod@betternotify/core exports createNotify() and createClient(). @betternotify/discord adds the Discord channel and Webhook API transport. zod defines route input schemas used throughout these docs.
Add a Discord webhook
Create an incoming webhook in the Discord channel you want to notify, then add it to .env:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...Discord destinations are configured on the transport, so sends do not need a to field.
Define a channel and send
Create a minimal setup to verify that the webhook can deliver:
import { createNotify, createClient } from '@betternotify/core';
import { discordChannel, discordTransport } from '@betternotify/discord';
import { z } from 'zod';
const discord = discordChannel();
const rpc = createNotify({ channels: { discord } });
const catalog = rpc.catalog({
deployAlert: rpc
.discord()
.input(z.object({ service: z.string(), version: z.string(), status: z.string() }))
.body(({ input }) => `Deployed **${input.service}** ${input.version}`)
.embeds(({ input }) => [
{
title: `${input.service} ${input.version}`,
description: `Status: ${input.status}`,
color: input.status === 'healthy' ? 0x57f287 : 0xed4245,
timestamp: new Date().toISOString(),
},
]),
});
const notify = createClient({
catalog,
transportsByChannel: {
discord: discordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_URL! }),
},
});
const result = await notify.deployAlert.send({
input: { service: 'api', version: 'v2.4.0', status: 'healthy' },
});
console.log('sent!', result.messageId);A delivered Discord message confirms your setup.
Confirm your runtime
Use Node 22 or newer, or Bun.
For new projects, set ESM in package.json:
{
"type": "module"
}Install the packages
npm install @betternotify/core @betternotify/slack zod@betternotify/core exports createNotify() and createClient(). @betternotify/slack adds the Slack channel and Web API transport. zod defines route input schemas used throughout these docs.
Add Slack credentials
Create a Slack app with the chat:write bot scope, install it to your workspace, then add the bot token and destination channel to .env:
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_CHANNEL=#releasesYour bot must be installed in the workspace and allowed to post to SLACK_CHANNEL.
Define a channel and send
Create a minimal setup to verify that the bot can deliver:
import { createNotify, createClient } from '@betternotify/core';
import { slackChannel, slackTransport } from '@betternotify/slack';
import { z } from 'zod';
const slack = slackChannel();
const rpc = createNotify({ channels: { slack } });
const catalog = rpc.catalog({
deployAlert: rpc
.slack()
.input(z.object({ service: z.string(), version: z.string(), status: z.string() }))
.text(({ input }) => `${input.service} deployed ${input.version}`)
.blocks(({ input }) => [
{ type: 'header', text: { type: 'plain_text', text: `Deploy: ${input.service}` } },
{
type: 'section',
text: { type: 'mrkdwn', text: `Version *${input.version}* is now ${input.status}.` },
},
]),
});
const notify = createClient({
catalog,
transportsByChannel: {
slack: slackTransport({ token: process.env.SLACK_BOT_TOKEN! }),
},
});
const result = await notify.deployAlert.send({
to: process.env.SLACK_CHANNEL!,
input: { service: 'api', version: 'v2.4.0', status: 'live' },
});
console.log('sent!', result.messageId);A delivered Slack message confirms your setup.
SMS installation docs are TBD.
Start with the email path above.
Push installation docs are TBD.
Start with the email path above.
What you have now
You can now write notification routes:
- Node
>= 22(or Bun) and ESM are in place. - Better-Notify core, a channel adapter, and a transport are installed.
- Your app knows where to read provider credentials.
Where to go next
- Your First Email breaks down how channels, routes, sub-catalogs, and the typed client work together — useful whether you scaffolded with the CLI or installed manually.
- Channels explains how email, SMS, push, and custom channels are defined.
- Transports covers provider adapters, multi-transport composition, and failover.
Other packages
These are the next common additions after the email + SMTP example:
@betternotify/smsfor SMS routes.@betternotify/pushfor push notification routes.@betternotify/telegramfor Telegram bot notifications.@betternotify/discordfor Discord webhook notifications.@betternotify/slackfor Slack Web API notifications.@betternotify/react-emailfor React Email templates.@betternotify/resendfor Resend as a transport instead of SMTP.