@thinwrap/notifications

@thinwrap/notifications for TypeScript — unified facade over 35 notification providers across email, SMS, push, and chat. Stateless, zero runtime deps, BYO fetch.

TypeScript constraints

Runtime dependencies
Zero runtime dependencies
BYO HTTP client
injectable fetch parameter (Node 18+ native fetch)
Provenance
npm provenance + Sigstore
Minimum runtime
Node 18.20+
  • Node 18 and 20 emit a one-time ExperimentalWarning on the first fetch() invocation: "ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time."
  • The warning is informational only — fetch() in Node 18.20+ and Node 20.x is functionally identical to the stable fetch in Node 21+. Suppress with the NODE_NO_WARNINGS=1 env var or the --no-warnings flag if it clutters CI logs.
  • Thinwrap does not patch around the warning, does not silence it on the consumer's behalf, and does not require a polyfill. It is surfaced exactly as Node's runtime surfaces it.

30-second install

npm install @thinwrap/notifications

2-minute end-to-end example

Send an email through any supported provider with one normalized call. The example below uses SendGrid; switching providers means changing the provider ID and config — input and output shape stay identical.

import { Email } from '@thinwrap/notifications';

const email = new Email('sendgrid', {
  apiKey: process.env.SENDGRID_API_KEY!,
  from: 'you@example.com',
});

await email.send({
  to: 'user@example.com',
  subject: 'Hello',
  text: 'Hello world',
});

Same code works for all supported providers — change the provider ID and pass the matching config. The supported-providers list below is auto-sourced from the catalog and stays in lockstep with what the package actually exports.

See the universal email snippet for the full copy-paste pattern.

Supported providers

35 providers ship with @thinwrap/notifications@1.0.2.
ProviderChannelAuthREADME
sendgrid SendGridemailbearerdocs
ses AWS SESemailaws-sig-v4docs
mailgun Mailgunemailbasicdocs
postmark Postmarkemailheaderdocs
resend Resendemailbearerdocs
brevo Brevo (Sendinblue)emailheaderdocs
mailersend MailerSendemailbearerdocs
mailtrap Mailtrapemailbearerdocs
sparkpost SparkPostemailheaderdocs
scaleway Scalewayemailheaderdocs
twilio Twiliosmsbasicdocs
vonage Vonagesmsquerydocs
plivo Plivosmsbasicdocs
sns AWS SNSsmsaws-sig-v4docs
messagebird MessageBird (Bird)smsheaderdocs
infobip Infobipsmsheaderdocs
telnyx Telnyxsmsbearerdocs
sinch Sinchsmsbearerdocs
textmagic Textmagicsmsheaderdocs
d7networks D7 Networkssmsbearerdocs
fcm Firebase Cloud Messagingpushrs256-jwtdocs
apns Apple Push Notificationspushes256-jwtdocs
expo Expo Pushpushbearerdocs
one-signal OneSignalpushheaderdocs
pusher-beams Pusher Beamspushbearerdocs
wonderpush WonderPushpushbearerdocs
slack Slackchatwebhook-urldocs
discord Discordchatwebhook-urldocs
ms-teams Microsoft Teamschatwebhook-urldocs
telegram Telegramchatwebhook-urldocs
whatsapp-business WhatsApp Businesschatbearerdocs
line LINEchatbearerdocs
google-chat Google Chatchatwebhook-urldocs
rocket-chat Rocket.Chatchatwebhook-urldocs
mattermost Mattermostchatwebhook-urldocs

Migration paths

From @novu/providers

Novu compatibility is design inspiration credit, not a critical path — the import change is usually one line:

- import { SendgridEmailProvider } from '@novu/providers';
+ import { Email } from '@thinwrap/notifications';
- const provider = new SendgridEmailProvider({ apiKey });
+ const provider = new Email('sendgrid', { apiKey, from });

From @sendgrid/mail (or any vendor SDK)

Drop the vendor SDK; the wrapper produces the same outbound HTTP call with no transitive dependencies. Vendor-specific fields are accessible through the typed _passthrough escape valve.

- import sgMail from '@sendgrid/mail';
- sgMail.setApiKey(process.env.SENDGRID_API_KEY);
- await sgMail.send({ to, from, subject, text });
+ import { Email } from '@thinwrap/notifications';
+ const email = new Email('sendgrid', { apiKey: process.env.SENDGRID_API_KEY!, from });
+ await email.send({ to, subject, text });

From hand-rolled fetch

Keep your retry/observability layer; replace the per-vendor request shape with a single uniform call. The wrapper performs no automatic retry — compose your own from ConnectorError.providerCodeand the raw Retry-After header on e.cause.

Read more