Model Context ProtocolMCP support is here: let AI agents discover and send notifications through your typed catalog

Typed notifications for Node.js and Bun

Define one Catalog, send typed notifications across email, WhatsApp, SMS, Telegram, Slack, and Discord. Pick your transport: SMTP, Resend, Cloudflare, or your own.

$npx create-better-notify@latest

Scaffolds a new project with routing, transports, and templates.

Runs on
Node.js
·
Bun
·
Cloudflare Workers
·
Vercel
In production at
import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { smtpTransport } from '@betternotify/smtp';

const notify = createNotify({
  channels: { email: emailChannel() }
});

const catalog = notify.catalog({
  welcome: notify.email()
    .input(z.object({ name: z.string() }))
    .subject(({ input }) => `Welcome, ${input.name}`)
    .template(WelcomeEmail)
});

const client = createClient({
  catalog,
  transportsByChannel: {
    email: smtpTransport({ host: '...' })
  }
});

// fully typed: TS knows welcome expects { name: string }
await client.welcome.send({
  to: 'ada@example.com',
  input: { name: 'Ada' }
});

Works with

Cloudflare
WhatsApp
Telegram
Discord
Slack
Resend
AWS SES
Twilio
Mailchimp
OneSignal
Autosend
Unosend
Selligent
Zapier
SMTP
SMS
Push
Model Context ProtocolMCP
Cloudflare
WhatsApp
Telegram
Discord
Slack
Resend
AWS SES
Twilio
Mailchimp
OneSignal
Autosend
Unosend
Selligent
Zapier
SMTP
SMS
Push
Model Context ProtocolMCP
Cloudflare
WhatsApp
Telegram
Discord
Slack
Resend
AWS SES
Twilio
Mailchimp
OneSignal
Autosend
Unosend
Selligent
Zapier
SMTP
SMS
Push
Model Context ProtocolMCP
Cloudflare
WhatsApp
Telegram
Discord
Slack
Resend
AWS SES
Twilio
Mailchimp
OneSignal
Autosend
Unosend
Selligent
Zapier
SMTP
SMS
Push
Model Context ProtocolMCP

Why Better-Notify

A library, not a platform.

No dashboard, no SaaS, no vendor lock-in. Define notifications in code and let the type system catch the rest.

End-to-end typed

One catalog types your sending across every channel. Refactor a route and every call site updates.

const catalog = rpc.catalog(({ welcome, invoice, alert })

Channel-agnostic

Email, SMS, push, or your own channel. Same pipeline, different transport.

Composable middleware

Rate limit, retry, dedupe, log. Wrap any send with the same primitives.

Provider failover

multiTransport tries Resend, then Cloudflare, then SMTP. Health-checked, retry-aware.

Schema-first inputs

Zod, Valibot, or any Standard Schema. Validated before any side effect.

Plays well with everything

Express, Hono, tRPC, queue workers. ESM-only, Node 22+ & Bun, zero opinions.

Channels

Same API. Any channel.

Each channel is a package with its own rendering and transport. Swap providers without touching route definitions. Build your own with defineChannel() when the built-ins don't fit.

ready

Email

@betternotify/email

Typed templates with React Email, MJML, or plain functions. Multi-provider failover out of the box.

Resend · Cloudflare · SMTP · Mailchimp · React Email

new

WhatsApp

@betternotify/whatsapp

Send text, media, templates, and interactive messages through Meta Cloud API. Same builder and pipeline as every other channel.

Meta Cloud API · Text · Media · Templates · Interactive

ready

SMS

@betternotify/sms

Twilio

soon

Push

@betternotify/push

APNs · FCM · Web Push

ready

Telegram

@betternotify/telegram

Bot API · Text · Photos · Documents

ready

Discord

@betternotify/discord

Webhook API · Embeds · Username override

ready

Slack

@betternotify/slack

Bot API · Block Kit · Threads

ready

Zapier

@betternotify/zapier

Webhooks · Automations · 7000+ apps

always

Custom

defineChannel()

Webhooks · in-app

The pipeline

One flow. Every channel.

Every send goes through the same five phases: input validation, render, middleware, transport, result. Hooks observe; middleware controls.

mail.welcome.send()SMTP · Gmail
Validate
Standard Schema
Render
React Email · template
Middleware
rate-limit · retry · log
Transport
Resend · Cloudflare · SMTP
Result
messageId · attempts
Hooks

Observe without altering the send. onBeforeSend, onExecute, onAfterSend, onError.

Middleware

Wrap, gate, retry, mutate. If removing it would change whether a message goes out, it's middleware, not a hook.

Transports

Pluggable per-channel. multiTransport composes failover, round-robin, race, parallel, mirrored.

AI-native

Your catalog is their toolbox.

Expose your notification routes as MCP tools. Claude, Cursor, Copilot — any agent that speaks the Model Context Protocol can discover and send notifications through your typed catalog.

catalog.ts
// your catalog — same as always
const catalog = rpc.catalog(({
  transactional: rpc.catalog(({
    welcome: rpc
      .email()
      .input(z.object(({ name: z.string() }))
      .subject('Welcome!')
      .template(WelcomeEmail),
  }),
});
Model Context Protocolwhat AI agents see
Model Context Protocoltools/list
transactional.welcome.send

Send email notification via welcome

transactional.welcome.render

Preview email notification for welcome without sending

resources
notifications://recent
notifications://stats

Zero config discovery

Each catalog route becomes a typed tool. Input schemas, descriptions, and validation — automatic.

Same pipeline, new caller

Middleware, hooks, failover — all active. Agent sends go through the exact same execution path.

stdio or HTTP

Run locally as a subprocess or deploy as a network service with bearer auth and streaming.

Comparison

When to reach for it.

Better-Notify sits between cloud notification platforms like Novu, Knock, and Courier — and a hand-rolled sendEmail() helper.

Cloud platformsNovu · Knock · CourierHand-rolledBetter-Notify
Lives in your codebase— dashboard
End-to-end typed call sites— stringly-typedpartial✓ catalog → sender
Multi-provider failover✓ vendor-locked— DIY✓ multiTransport()
Multi-channel from one routevaries— DIY✓ shared pipeline
Validation before side effectspartial— DIY✓ Standard Schema
Vendor lock-inhighnonenone
Pricing modelper-sendfreefree · MIT

Quick start

Install. Define. Send.

Three packages, three minutes. Email, SMS, Telegram, Slack, and Discord channels ship today.

1

Bootstrap your project

$npx create-better-notify@latest

Scaffolds a new project with routing, transports, and templates.

or install manually
2

Install

$pnpm add @betternotify/core @betternotify/email @betternotify/smtp
3

Define your catalog

const welcome = rpc.email()
  .input(WelcomeSchema)
  .subject(({ n }) => `Hi, ${n}`)
  .template(WelcomeAdapter);
4

Send anywhere

await mail.welcome.send({
  to: 'ada@example.com',
  input: { name: 'Ada' },
});

Open Source

Notifications, from your code.

Read the docs, follow the quickstart, ship a typed sender by lunch. MIT licensed; no signup; no dashboard.