Installation
Get Started

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@latest

The 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 credentials

Run the generated project to send your first notification:

npx tsx src/index.ts

The 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:

.env
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=mailer@example.com
SMTP_PASS=your-password

The 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:

src/index.ts
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:

.env
TELEGRAM_BOT_TOKEN=123456789:your-bot-token
TELEGRAM_CHAT_ID=-1001234567890

Use 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:

src/index.ts
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:

.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:

src/index.ts
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:

.env
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_CHANNEL=#releases

Your 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:

src/index.ts
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/sms for SMS routes.
  • @betternotify/push for push notification routes.
  • @betternotify/telegram for Telegram bot notifications.
  • @betternotify/discord for Discord webhook notifications.
  • @betternotify/slack for Slack Web API notifications.
  • @betternotify/react-email for React Email templates.
  • @betternotify/resend for Resend as a transport instead of SMTP.

On this page