v1.2.2open source · ESM-only · zero runtime deps
Notification infrastructure that runs everywhere your code does
One TypeScript API for Email, SMS, WhatsApp, and Push. Write once — switch providers without rewriting app code.
One package — sently — sently/mailer · sently/sms · sently/whatsapp · sently/push
- Runs on
- Node
- Bun
- Deno
- Workers
import { createMailer } from "sently/mailer";
import { ResendTransport } from "sently/transports/resend";
const mailer = await createMailer({
transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
});
await mailer.send({
from: "hello@example.com",
to: "you@example.com",
subject: "Welcome",
html: "<p>Sent with sently.</p>",
});Swap the transport — app code stays on sently.
1/8Animated code tour cycling through email, SMS, WhatsApp, and push snippets to show the same sender shape across every channel. Hover or focus the panel to pause.
- live verified
- available
- sponsors soon
- Resend
- SendGrid
- Postmark
- Mailgun
- AWS SES
- Mailpit (live verified)
- Inbucket (live verified)
- SNDR (live verified)
- Hostinger (live verified)
- Taqnyat (live verified)
- Web Push (live verified)
- Brevo
- SMTP
- Cloudflare Email
- SparkPost
- MailerSend
- Loops
- Plunk
- Mailtrap
- Twilio
- Msegat
- Unifonic
- WhatsApp Cloud
- FCM
Channel senders, not vendor SDKs
Apps call createMailer, createSmsSender, createWhatsAppSender, or createPushSender. Providers are transports under those senders.
- sently/mailer · sently/smtp
- channels
- sently/sms · sently/whatsapp · sently/push
- 0
- channels, one sender shape
- 0
- provider transports
- 0
- runtime dependencies
- ~0 KB
- HTTP email stack, gzip
Why teams outgrow a single SDK
The pain is not sending once — it is keeping call sites stable as channels and providers change.
- Different SDKs everywhere
Every provider ships its own client. Sently exposes one sender shape per channel instead.
- Per-vendor error shapes
Catch SentlyError codes like RATE_LIMITED — not a different string format per vendor.
- Reliability bolted on later
Retry and fallback wrap any transport. Same decorators when you add the next channel.
- Runtime lock-in
One ESM package on Node, Bun, Deno, and Cloudflare Workers — including SMTP where sockets need adapters.
Every vendor ships its own auth, retries, and error strings into your codebase.
Senders own the contract; transports plug in underneath. Failures arrive as one shape.
A library, not a platform. Preference centers, digests, and workflow builders (custom or Novu, Knock, Courier) sit on top of sently — not instead of it.
App → channel sender → transport
Pass a provider transport into the sender, then call send. Hooks observe; decorators add retry and fallback without a new API.
- 01SendercreateMailer
- 02TransportResendTransport
- 03Sendmailer.send()
- 04ResultChannelSendResult
- onSend
- onSuccess
- onError
- onRetry
Animated demonstration: a send travels sender, transport, send, result. The cycle shows a plain success, a rate-limit retried by RetryTransport, and a primary outage rerouted by FallbackTransport, with onSend, onSuccess, onError, and onRetry hooks firing in turn.
- 01SendercreateMailer · createSmsSender · createWhatsAppSender · createPushSender
- 02TransportResend · SMTP · Twilio · WhatsApp Cloud · Web Push — or your own
- 03Sendmailer.send() · sms.send() — same shape, channel-specific options
- 04ResultmessageId · accepted · provider response — stable error codes
Observe without changing the send — onSend, onSuccess, onError, onRetry. No body or PII in hook context.
Wrap any transport — RetryTransport, FallbackTransport, IdempotencyTransport. Same contract, stronger send path.
Four channels. Same shape.
Vendor extras (OTP helpers, account utilities) stay on the concrete transport — never on the shared sender contract.
- EmailreadycreateMailerSMTP or HTTP providers — Resend, Hostinger, SES, SendGrid, and more.Resend · Hostinger · SMTP · SES · SendGrid · Postmark
- SMSreadycreateSmsSenderTwilio, Taqnyat, Msegat, Unifonic — one sender contract.Twilio · Taqnyat · Msegat · Unifonic
- WhatsAppreadycreateWhatsAppSenderCloud API or Taqnyat — templates and text.WhatsApp Cloud · Taqnyat
- PushreadycreatePushSenderWeb Push (VAPID) or FCM device tokens.Web Push · FCM
Measured, not marketed
Four hard caps CI fails on — HTTP stack, SMTP, SMS, and push — then channel entrypoints and sently/transports/* as pickable imports. Every figure is from bundle-size-budgets.json.
bundle-size-budgets.json- HTTP
6.8 KB
of 6.8 KB·100% used
mailer + Resend · gzip
- SMTP
15.8 KB
of 15.8 KB·100% used
createSMTPMailer · gzip
- SMS
0.8 KB
of 0.8 KB·100% used
createSmsSender · gzip
- Push
1.4 KB
of 1.4 KB·100% used
createPushSender · gzip
- sently/sms0.8 KB5%
- sently/whatsapp0.8 KB5%
- sently/push1.4 KB9%
- sently/mailer2.8 KB18%
- sently2.8 KB18%
- mailer + SNDR4.5 KB28%
- sently/smtp15.8 KB100%
CI-enforced gzip ceilings · isolated imports · unused transports stay out
Start here
Install, pick a channel, send. Details live in the handbook.
$ bun add sently
Send your first message in five minutes
Install sently, pick a channel sender, pass a transport. Five lines to the first send.