Introduction
Typed notifications for Node.js and Bun
Better-Notify is typed notification infrastructure for Node.js and Bun. You define a notification catalog once, then use the generated client to send email, SMS, push, Telegram, or custom-channel messages through one consistent pipeline.
Most notification setups start simple — a function that calls an API — then grow into a maze of duplicated validation, scattered templates, and provider-specific glue code. Better-Notify exists to prevent that drift. It keeps route definitions, input validation, rendering, middleware, hooks, and transports aligned so your notification layer stays type-safe as it grows.
If you have used tools like tRPC or oRPC, the shape should feel familiar: define contracts once, then consume them through a typed runtime API.
The problem
Notification code tends to evolve the same way across projects:
- You start with a helper function that sends one email through one provider.
- You add a second email type, duplicate half the logic, and tweak the template.
- You need SMS or push, so you add a second provider with its own validation and error handling.
- Someone asks for rate limiting, retry logic, or observability — and now every send path needs it.
- You swap providers and discover that templates, address handling, and error shapes are all coupled to the old one.
Each step is reasonable on its own. The result is a notification layer where every change requires touching multiple files, every new route copies boilerplate from the last one, and swapping a provider means rewriting half the pipeline.
Better-Notify solves this by separating concerns into layers that compose through types instead of coupling through implementation.
How it works
Better-Notify has four core pieces:
- Channels define how a message is shaped and rendered. Email has
to,subject,html. SMS hastoandbody. Push hastitle,body, anddata. Telegram hasto,body, and optionalattachment. You can define custom channels for Slack, webhooks, or anything else. - Routes are contracts for individual notifications. Each route declares which channel it uses, what input it accepts, and how it renders. Routes compose into catalogs, which flatten into dot-path IDs like
transactional.welcome. - Transports deliver rendered messages to providers. SMTP, Resend, or your own adapter. Transports never see raw input — they receive fully validated, fully rendered messages.
- The client ties it all together.
createClienttakes a catalog and a transport per channel, then exposes typed.send()methods for every route in the catalog.
The separation is the point. You can swap providers without touching routes. You can add middleware without changing templates. You can test with a mock transport and deploy with a multi-provider failover — same catalog, same client shape.
Where to go next
Installation
Install Better-Notify and set up your first channel.
Your First Email
Build a working email setup with channels, routes, sub-catalogs, and a typed client.
Channels
Understand email, SMS, push, and custom channel definitions.
Transports
Learn how transports deliver messages and how to compose them.
Examples
Browse working examples covering single sends, multi-transport, middleware, custom channels, and more.