Compared with alternatives
@alexify/messenger is being built in the place of the libraries below, with a reliability layer none of them has whole. The table compares eleven Node.js solutions in four directions: unified libraries over many channels and providers (@visulima/notification, @thinwrap/notifications, notifme-sdk), a notification platform with a code-first SDK (Novu), the official SDKs of five providers of SMS and WhatsApp (Twilio, Telnyx, Vonage, Plivo, Sinch), and two Telegram libraries (grammY, node-telegram-bot-api). As in the comparison of @alexify/mailer, every feature is in one table rather than a few convenient libraries in a few convenient rows.
What the table shows
- No library has one model of delivery receipts. @visulima/notification parses the statuses of Twilio alone, and maps an unknown event of Standard Webhooks, and every Slack and SNS event, to
delivered; @thinwrap/notifications exports status enums it never uses and reports every SMS assent; the SDKs type their own receipts at best (Telnyx, Sinch). Telegram has no receipts at all. - Fallback between channels moves on only when a send fails. @visulima/notification's
route()stops at the first send that did not throw, a suppressed or deduplicated one included; Novu's steps can skip only on the read state of its in-app channel; notifme-sdk sends every channel at once. The providers that fall back on the outcome (Vonage'sfailover, Sinch'schannel_priority_order, RCS to SMS at Twilio and Telnyx) do it inside their own network. - A retry can send a message twice. Telnyx retries a POST without an idempotency key unless one is passed, node-telegram-bot-api retries
sendMessageafter a timeout, and grammY's auto-retry plugin retries network errors without a limit. Only Telnyx takes anIdempotency-Key; @visulima/notification deduplicates in memory. - Nothing is recorded outside Novu's server. No library keeps a log of the states of a message. @visulima/notification has adapters for four queues, but reading its code shows a worker that sends every channel again when one failed, reservations that never expire, and a BullMQ adapter that calls a method a
Queuedoes not have. - Webhook signatures are checked unevenly. Vonage (SMS), Plivo and Sinch compare signatures with
===rather than in constant time; Sinch never checks the age of its timestamp; Vonage checks the JWT but not the hash of the body; Novu's bridge skips its HMAC unlessNODE_ENVsays production; grammY and @visulima/notification accept any request when no Telegram secret is set, where node-telegram-bot-api refuses to start without one. - The rules of a channel are left to the application or the provider. No library checks an E.164 number or counts the segments of an SMS itself; the SDKs offer the providers' lookup and encoding APIs. WhatsApp's Cloud API is reached directly only by @thinwrap/notifications and Novu, and no library tracks the 24-hour window.
- Meta's channels, Viber and Ukraine come with single providers. No unified library sends to Messenger, Instagram or Viber: Messenger is in the SDKs of Twilio, Vonage and Sinch, Instagram in Sinch's alone, Viber in Vonage's and Sinch's, and Telegram among the SDKs in Sinch's alone. No library has TurboSMS, AlphaSMS, SMSClub or SMS-Fly.
- Work chats are mostly incoming webhooks. None of the five SDKs posts to Slack, Discord, Teams or Google Chat. @thinwrap/notifications reaches all four, through their incoming webhooks only; @visulima/notification has Slack's Web API but Discord through a webhook alone, and posts to Teams in the legacy
MessageCardof the Office 365 connectors unless given an Adaptive Card; Novu runs Slack, Discord and Teams on its server and lists Google Chat as coming soon. - Push is uneven. @visulima/notification sends FCM, APNs and Web Push; @thinwrap/notifications has no Web Push of its own and needs an HTTP/2
fetchinjected for APNs; notifme-sdk's FCM path reads settings it never sets, from reading its code; of the SDKs, only Twilio's Notify sends push. - Zero dependencies is not rare, CommonJS with state is. @visulima/notification, @thinwrap/notifications, Telnyx and node-telegram-bot-api 2 have none; but the first is ESM only, and the others send and keep nothing. Twilio brings axios, Vonage seventeen packages, and Plivo fifteen, the deprecated
requestamong them.
This is the gap @alexify/messenger is built for; see Why @alexify/messenger?.
The table
| Feature | @alexify/messenger unified + reliability | @visulima/notification 2 unified, multi-channel | @thinwrap/notifications 1 stateless send facade | @novu/framework 2 workflows + Novu server | notifme-sdk 1 unified, multi-channel | grammy 1 Telegram bot framework | node-telegram-bot-api 2 Telegram Bot API client | twilio 6 Twilio SDK | telnyx 7 Telnyx SDK | @vonage/server-sdk 3 Vonage SDK | plivo 4 Plivo SDK | @sinch/sdk-core 1 Sinch SDK |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Runtimes (from imports and package metadata, not run) | ||||||||||||
| Node.js, minimum version | ✅ ≥22, CI on 22 and 24 | ✅ ≥22.14; not 23 or 24.0–24.91 | ✅ ≥18 | ⚠️ not declared (no engines) | ⚠️ not declared (no engines) | ✅ ≥12.20 / ≥14.13.1 | ✅ ≥18 | ✅ ≥20 | ⚠️ not declared2 | ✅ ≥22 (engineStrict) | ⚠️ not declared | ⚠️ not declared |
| Deno | ✅ CI: unit and runtime suites3 | ❌ dropped in 2.0.04 | ❌ not declared | ❌ not declared | ❌ not declared | ✅ Deno-first (deno.land/x/grammy) | ✅ declared in README, web-standard core | ❌ not declared | ✅ declared (README), fetch-based | ⚠️ deno export condition only5 | ❌ not declared | ❌ not declared |
| Bun | ✅ CI: unit and runtime suites | ✅ declared; fetch + Web Crypto | ❌ not declared | ❌ not declared | ❌ not declared | ✅ bun webhook adapter | ✅ declared, tests use bun test | ❌ not declared | ✅ declared (README) | ⚠️ bun export condition only | ❌ not declared | ❌ not declared |
| Cloudflare Workers / edge | ❌ not a target yet | ✅ declared; no node:* outside APNs6 | ⚠️ crypto import; needs nodejs_compat7 | ⚠️ Hono/h3 adapters, but imports Node built-ins8 | ❌ node-fetch, nodemailer, crypto | ✅ web bundle, cloudflare-mod adapter | ✅ core has no node:* | ❌ Node https, fs, crypto9 | ✅ Workers, Vercel Edge declared; fetch only | ⚠️ workerd condition, but Node APIs10 | ❌ Node http/https, axios agents | ❌ node-fetch 2, Node crypto/stream |
| Browser | ❌ credentials stay on a server | ❌ not declared | ❌ server-only (README) | ❌ server-side bridge | ❌ | ✅ browser export (out/web.mjs) | ⚠️ web-standard core, not declared | ❌ Node-only | ✅ declared; no browser guard11 | ❌ browser export throws (Node-only) | ❌ Node-only | ❌ Node-only |
| Channels | ||||||||||||
| SMS | 🔜 Twilio, Vonage, Infobip, Sinch, SNS | ✅ Twilio, Vonage, Plivo, MessageBird, Telnyx, SNS | ✅ 10, incl. Twilio, Vonage, Infobip, Sinch | ✅ 38 SMS provider ids, on Novu server | ✅ 9 providers | — | — | ✅ Messages API | ✅ Messages API (long/short code, alphanumeric) | ✅ Messages API and SMS API | ✅ Message API | ✅ SMS API (batches) |
| RCS | 🔜 Google RBM, the SMS providers' RCS | ❌ no RCS code12 | ⚠️ Twilio contentSid via a Messaging Service13 | ❌ no provider id | ❌ | — | — | ✅ Messages API, RCS senders (rcs:) | ✅ messages.rcs.send(), RCS agents | ✅ Messages API rcs channel14 | ❌ none | ✅ Conversation API RCS |
| 🔜 Meta Cloud API | ⚠️ Twilio whatsapp: prefix only15 | ✅ Meta Cloud API | ✅ Cloud API (whatsapp-business)16 | ✅ Infobip (BSP) only | — | — | ✅ official, Twilio as BSP | ✅ official, messages.whatsapp() | ✅ official, Messages API | ✅ official, type: 'whatsapp' | ✅ Conversation API (official BSP) | |
| Facebook Messenger | 🔜 Send API of a page | ❌ | ❌ | ❌ no provider id | ❌ listed as a TODO in the source | — | — | ✅ Conversations messenger address type17 | ❌ none | ✅ Messages API messenger channel | ❌ none | ✅ Conversation API MESSENGER |
| Instagram Direct | 🔜 Instagram messaging API | ❌ | ❌ | ❌ no provider id | ❌ | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ✅ Conversation API INSTAGRAM |
| Telegram Bot API | 🔜 Bot API | ✅ sendMessage text + webhook receiver18 | ✅ Bot API sendMessage | ✅ telegram chat provider and agents19 | ❌ listed as a TODO in the source | ✅ Bot API 10.3 | ✅ Bot API 10.3 | ❌ none | ❌ none | ❌ none | ❌ none | ✅ Conversation API TELEGRAM channel |
| Viber | 🔜 Bot API, Ukrainian providers | ❌ | ❌ | ❌ no provider id | ❌ | — | — | ❌ none | ❌ none | ✅ viber_service (Viber Business) | ❌ none | ✅ VIBER, VIBERBM channels |
| Slack | 🔜 Web API and incoming webhooks | ✅ Web API chat.postMessage or incoming webhook20 | ✅ incoming webhook only; blocks, threads21 | ✅ slack, novu-slack: OAuth app, webhooks, Novu server22 | ✅ incoming webhook only | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Discord | 🔜 bot REST API and webhooks | ✅ incoming webhook only, plus interactions receiver23 | ✅ incoming webhook only; embeds, components | ✅ discord: incoming webhook, on the Novu server24 | ❌ | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Microsoft Teams | 🔜 Workflows webhooks, Bot Framework | ✅ incoming webhook + outgoing-webhook receiver25 | ✅ Workflows or legacy webhook, Adaptive Card26 | ✅ msteams: Azure Bot, on the Novu server27 | ❌ | — | — | ❌ none | ❌ none (Teams Phone voice only)28 | ❌ none | ❌ none | ❌ none |
| Google Chat | 🔜 space webhooks, Chat API | ❌ | ✅ space webhook; cards v2, threads | 🔜 google-chat is marked comingSoon29 | ❌ | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Push (FCM, APNs, Web Push) | 🔜 FCM HTTP v1, APNs, Web Push30 | ✅ FCM HTTP v1, APNs, Web Push, Expo31 | ✅ FCM v1, APNs, Expo, OneSignal, Beams, WonderPush32 | ✅ fcm, apns, expo, one-signal, pushpad…, Novu server33 | ⚠️ APNs, Web Push (VAPID), WNS, ADM; FCM broken34 | — | — | ✅ Notify: APNs, FCM (GCM legacy)35 | ❌ none (call push credentials only)36 | ❌ none | ❌ none | ❌ none |
| Other channels | ❌ later, when a user needs one37 | ✅ email, in-app inbox, outbound webhook38 | ✅ email, LINE, Mattermost, Rocket.Chat39 | email, in-app, LINE, iMessage, Webex, Mattermost, Zulip…40 | email, voice (Twilio) | — | — | ✅ MMS, voice, video, email, in-app chat, Apple Messages41 | ✅ MMS, voice, video rooms, fax, email | ✅ MMS, email, voice, video | ✅ MMS, voice, number masking | ✅ MMS, LINE, WeChat, KakaoTalk, Apple, voice, fax42 |
| Ukrainian providers | 🔜 TurboSMS, AlphaSMS, SMSClub | ❌ | ❌ | ❌ | ❌ | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Providers and delivery reliability | ||||||||||||
| Providers, count | 🔜 few per channel | 16 + email via @visulima/email43 | 35 across SMS, chat, push, email44 | 86 provider ids (38 SMS)45 | 24 plus custom46 | 1 (Telegram Bot API) | 1 (Telegram Bot API) | 1 (its own) | 1 (its own) | 1 (its own) | 1 (its own) | 1 (its own) |
| Failover between providers | 🔜 between providers of a channel | ✅ createFailoverProvider, round-robin47 | ❌ | ❌ routing by primary/conditions, no failover48 | ✅ fallback (default), roundrobin, custom function | — | — | ❌ single provider | ❌ single provider | ❌ single provider | ❌ single provider | ❌ single provider |
| Fallback between channels | 🔜 on refusal, or no receipt in time49 | ✅ route() best-of channel order50 | ❌ | ⚠️ step skip on in-app read state51 | ❌ channels are sent in parallel52 | — | — | ⚠️ provider-side RCS → SMS fallbackFrom | ⚠️ provider-side RCS → SMS/MMS fallback | ⚠️ provider-side failover array53 | ❌ none | ⚠️ provider-side channel_priority_order |
| Retry / backoff | 🔜 classified errors, Retry-After | ✅ HTTP retries + retryMiddleware54 | ❌ none; error codes + Retry-After exposed | ⚠️ on the Novu server55 | ❌ moves to the next provider, no backoff | ⚠️ plugin @grammyjs/auto-retry56 | ✅ 429, network and 5xx errors, jittered backoff57 | ⚠️ opt-in autoRetry, HTTP 429 only58 | ✅ 2 retries, backoff + jitter, Retry-After59 | ❌ none | ❌ none for messages60 | ⚠️ HTTP 429 only, Retry-After + jitter61 |
| Rate limiting | 🔜 per channel and provider | ✅ token-bucket middleware, in memory62 | ❌ | ⚠️ throttle step, on the Novu server | ❌ | ⚠️ plugin transformer-throttler (bottleneck) | ✅ opt-in token bucket, global and per chat | ❌ none (agent maxSockets only) | ❌ none | ❌ none | ❌ none | ❌ none |
| Circuit breaker | 🔜 | ⚠️ one breaker for all channels63 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Timeout / AbortSignal | 🔜 AbortSignal | ⚠️ per-provider timeout (30 s), no AbortSignal | ⚠️ only through an injected fetch | ❌ trigger fetch has no timeout or signal | ❌ node-fetch without a timeout | ✅ timeoutSeconds (500 s), AbortSignal | ✅ 300 s by default, AbortSignal | ✅ timeout (30 s default); no AbortSignal | ✅ 60 s default, per request; AbortSignal | ⚠️ opt-in timeout, no default64 | ⚠️ opt-in timeout, no default | ✅ timeoutSeconds (60 s default); no AbortSignal |
| Persistent queue (survives a restart) | 🔜 outbox, driver passed in | ✅ BullMQ, pg-boss, SQS, unstorage adapters65 | ❌ | ⚠️ jobs persisted on the Novu server | ⚠️ separate RabbitMQ plugin (2017)66 | ❌ no outbound queue | ❌ | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Messages | ||||||||||||
| Phone numbers checked (E.164) | 🔜 without libphonenumber-js | ❌ a type comment only | ❌ | ❌ subscriber phone is a free string | ❌ only strips + for Infobip | — | — | ⚠️ Lookups API (provider side) | ⚠️ Number Lookup API (provider side) | ⚠️ Number Insight API (provider side) | ⚠️ Lookup API (provider side) | ⚠️ Number Lookup API (provider side) |
| SMS encoding and segments | 🔜 GSM-7 / UCS-2, before sending | ❌ | ❌ | ❌ | ❌ a type: 'unicode' flag only | — | — | ⚠️ provider-side smartEncoded, numSegments | ⚠️ provider-side encoding, parts | ⚠️ provider-side encodingType | ❌ none | ⚠️ provider-side dryRun (parts, encoding) |
| Media | 🔜 | ⚠️ MMS URLs on Twilio, Telnyx67 | ⚠️ Twilio/Telnyx MMS; WhatsApp via passthrough | ⚠️ chat card images, agent file replies68 | ✅ WhatsApp media URLs, email attachments | ✅ InputFile, InputMediaBuilder | ✅ streaming InputFile, media groups | ✅ mediaUrl (MMS, WhatsApp) | ✅ media_urls, WhatsApp media | ✅ image, video, audio, file classes | ✅ media_urls/media_ids (MMS, WhatsApp) | ✅ media messages, MMS |
| Buttons / keyboards | 🔜 | ⚠️ raw Slack blocks, Teams cards69 | ✅ Telegram keyboards, WhatsApp interactive, Slack blocks | ✅ chat cards (Button), in-app actions | ⚠️ Slack and web-push actions only | ✅ InlineKeyboard, Keyboard | ✅ inline and reply keyboard builders | ✅ Content API (quick reply, card, list) | ✅ WhatsApp interactive, RCS suggestions | ✅ RCS suggestions, Viber button, WA custom | ✅ WhatsApp interactive (joi-validated) | ✅ choice, card, carousel, list messages |
| WhatsApp templates | 🔜 | ❌ | ✅ type: 'template', Meta's shape | ⚠️ raw template object pass-through70 | ✅ templateName/templateData (Infobip) | — | — | ✅ contentSid + contentVariables | ✅ template + templates API | ✅ WhatsAppTemplate class | ✅ template param (joi-validated) | ✅ Conversation templates API |
| WhatsApp 24-hour window | 🔜 from inbound timestamps | ❌ | ❌ error 131047 is just invalid_request | ❌ | ❌ | — | — | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Built-in text templates | 🔜 after the first release | ✅ , Liquid, Handlebars, i18n71 | ❌ | ✅ Liquid (liquidjs) in step controls | ⚠️ separate notifme-template (2017) | ⚠️ plugins: i18n, fluent | ❌ EntityBuilder formatting only | ⚠️ provider-side Content API only | ❌ none | ❌ none | ❌ none | ⚠️ provider-side parameters, templates API |
| Batch / bulk | 🔜 | ✅ sendMany with concurrency, multi-recipient to72 | ⚠️ push multi-token sendMessage() only | ⚠️ many subscribers or topics in to | ❌ | ❌ | ❌ | ⚠️ Notify toBinding (provider side) | ❌ none (group MMS only) | ❌ none | ⚠️ dst array joined with <73 | ⚠️ provider batches (to[]) |
| Scheduled sending | 🔜 a delay of the queue | ✅ scheduledAt on queue enqueue74 | ⚠️ provider sendAt (Twilio, Infobip, Sinch) | ⚠️ delay steps (cron, date), on the Novu server | ❌ | ❌ | ❌ | ⚠️ provider-side sendAt + scheduleType | ⚠️ provider-side send_at, cancel | ❌ none | ❌ none | ⚠️ provider-side SMS send_at |
| Idempotency key | 🔜 outbox + provider's own keys | ⚠️ in-memory dedupeMiddleware only75 | ❌ | ⚠️ transactionId, sent with the trigger76 | ❌ | ❌ the Bot API has none | ❌ the Bot API has none | ❌ none on Messages77 | ⚠️ typed Idempotency-Key (provider), not automatic | ❌ none | ❌ none | ❌ none |
| Delivery status and compliance | ||||||||||||
| Delivery receipts, one model | 🔜 one model, every channel | ⚠️ one event type; only Twilio parses status78 | ❌ status enums exported, never used79 | ⚠️ message.* events, on the Novu server80 | ❌ | — the Bot API has none | — the Bot API has none | ⚠️ provider status enum; callbacks untyped | ⚠️ one Telnyx model (SMS/MMS/RCS/WA) | ⚠️ provider-side; no Messages status types | ⚠️ provider-side only; untyped | ⚠️ typed per API (SMS, Conversation)81 |
| Status webhooks: parsing | 🔜 status of every provider | ⚠️ Twilio status, Standard Webhooks, SNS | ⚠️ callback URL options, no parsing82 | ⚠️ 13 providers parsed, on the Novu server83 | ⚠️ voice statusCallback pass-through only | — updates only, no status events | — updates only, no status events | ⚠️ statusCallback URL only; no parser | ✅ typed webhooks.unwrap() events84 | ⚠️ SMS DLR type only; no parser | ⚠️ status url param; no parser | ✅ parseEvent for SMS and Conversation |
| Webhook signature verification | 🔜 verified before parsing | ✅ HMAC, Ed25519, JWT, SNS certificates85 | ❌ | ⚠️ HMAC on the bridge only86 | ❌ | ✅ secretToken, constant-time compare87 | ✅ secret token required by default88 | ✅ validateRequest HMAC-SHA1, Express middleware89 | ✅ Ed25519 (WebCrypto), 5-minute window | ⚠️ JWT verifySignature; SMS HMAC90 | ⚠️ validateV3Signature HMAC-SHA256 + nonce91 | ⚠️ HMAC-SHA256 with nonce + timestamp92 |
| Inbound messages | 🔜 STOP and the WhatsApp window | ✅ 8 receivers: SMS, Twilio WhatsApp, chat | ❌ | ⚠️ agents' onMessage, through the Novu server93 | ❌ | ✅ long polling and 20+ webhook adapters | ✅ long polling and a web-standard webhook | ⚠️ TwiML MessagingResponse; no parser | ✅ typed message.received event | ⚠️ SMS InboundMessage type only | ⚠️ Plivo XML replies; no parser | ✅ MO events parsed (SMS, Conversation) |
| Opt-out (STOP) / suppression | 🔜 one list across channels | ⚠️ suppression hook; the list is yours94 | ❌ | ⚠️ subscriber preferences, on the Novu server | ❌ | ❌ | ❌ | ⚠️ provider-side (Messaging Service, consents API) | ⚠️ provider opt-out list (read only) | ❌ none | ❌ none | ⚠️ provider consents API, OPT_OUT events |
| Quiet hours / user preferences | 🔜 after the first release | ⚠️ per-channel opt-outs, no quiet hours | ❌ | ⚠️ preferences and weekly schedule, on the Novu server | ❌ | ❌ | ❌ | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Observability and extensibility | ||||||||||||
| OpenTelemetry | 🔜 | ✅ telemetryMiddleware: spans, counter, histogram95 | ❌ | ❌ | ❌ | ❌ debug logs only | ❌ DEBUG tracing only | ❌ none | ❌ none | ❌ none | ❌ none | ❌ none |
| Hooks / middleware | 🔜 | ✅ middleware via use() | ❌ none; wrap the injected fetch | ⚠️ step skip, provider overrides | ⚠️ customize callback, custom providers and strategies | ✅ middleware and API transformers | ✅ koa-style update middleware96 | ⚠️ custom httpClient only | ⚠️ custom fetch, logger only | ❌ none (global fetch, debug logs) | ❌ none | ✅ request/response plugins |
| Mock / test mode | 🔜 | ✅ mockProvider records sends, failWith | ⚠️ inject fetch or your own connector | ⚠️ preview with mocked payloads | ✅ notification catcher, logger provider97 | ⚠️ transformers, test DC, custom fetch | ⚠️ injectable fetch | ❌ none98 | ❌ none | ❌ none | ❌ none99 | ⚠️ Jest *ApiFixture classes |
| Local audit log / event journal | 🔜 append-only, numbers erasable | ⚠️ in-memory event store, fed by hand100 | ❌ | ⚠️ activity feed, on the Novu server | ❌ winston console logs only | ❌ | ❌ | ❌ none (debug console logging) | ❌ none (logger only) | ❌ none (debug logs) | ❌ none | ❌ none (logger only) |
| Quality, license, maturity | ||||||||||||
| TypeScript types | ✅ hand-written, tsd | ✅ built in | ✅ built in | ✅ built in | ⚠️ Flow in the package, @types/notifme-sdk | ✅ built in (TS source) | ✅ built in, generated from Bot API docs | ✅ built in | ✅ built in | ✅ built in | ✅ bundled .d.ts | ✅ built in |
| Runtime dependencies | 0 | 0 (11 optional peers)101 | 0 | 11 (ajv, liquidjs, sanitize-html, chat…)102 | 8 (winston, nodemailer, node-fetch 2, web-push…) | 4 (node-fetch 2, debug, abort-controller, types) | 0 | 6: axios, jsonwebtoken, qs, dayjs, …103 | 0 (optional peer ws)104 | 17 (all @vonage/*)105 | 15, incl. deprecated request106 | 9 (all @sinch/*)107 |
| Module format | CJS, ESM import | ESM only | ESM + CJS | ESM + CJS | CJS only (Babel) | CJS for Node, ESM web bundle | ESM + CJS | CJS | both (CJS + ESM) | both (CJS + ESM) | CJS | CJS |
| License | MIT | MIT | MIT | ISC | MIT | MIT | MIT | MIT | MIT | Apache-2.0 | MIT | Apache-2.0 |
| Version (2026-10-22) | 0.0.1, a placeholder | 2.0.0 | 1.2.0 | 2.14.0 | 1.16.25 | 1.46.0 | 2.1.0 (2.2.0-rc2 on next) | 6.2.0 | 7.28.0 | 3.30.1 | 4.79.0 | 1.7.0 |
| Last release | not published yet | 2026-10-15 | 2026-10-18 | 2026-09-28 | 2025-03-25 | 2026-08-26 | 2026-08-24 | 2026-10-12 | 2026-10-22 | 2026-09-09 | 2026-07-28 | 2026-10-15 |
| npm downloads / week | — | 14k | 494 | 43k | 705 | 2.5M | 125k108 | 3.7M | 238k | 73k | 23k | 11k |
| Owner / maintenance risk | 1 author, Alexis Technologies | 1 author (Anolilab), 52 versions since June | 1 author, 5 versions since June 2026109 | company (Novu), active | 2 maintainers, no release since Mar 2025 | 1 npm maintainer, active org | 2 maintainers, two rewrites in 2026110 | Twilio (company), active | Telnyx (company), Stainless-generated111 | Vonage (company), active | Plivo (company), active | Sinch (company), active |
✅ in the code · ⚠️ partial, or only on the provider side · ❌ none · 🔜 planned by the author, no code yet · — does not apply. Every cell was decided by reading the package as published on npm as of 2026-10-22, not its README; the column of @alexify/messenger is its plan as of the same date.
engines:^22.14.0 || >=24.10.0.- No
enginesfield; the bundled README lists Node.js 20 LTS or later. - Through
npm:@alexify/messenger. - The 2.0.0 major release exists only to remove Deno as a supported runtime (CHANGELOG).
- package.json maps a long list of runtime conditions (deno, bun, workerd, …) to the same build; the README only mentions Node.js.
- Every SMS and chat provider and every webhook verifier uses only
fetchand Web Crypto; APNs (node:http2) and the BullMQ / pg-boss / SQS queue adapters are Node-only. - The
SmsandPushfacades statically import the SNS, FCM and APNs connectors, which use Node'scryptoandBuffer; the chat connectors use onlyfetch. - The main entry statically imports
path,process,urlandasync_hooksand readsprocess.envandprocess.hrtime, so a Worker needsnodejs_compat. Nothing in the package declares edge support. Sending itself runs on the Novu server. lib/base/RequestClient.jsbuilds anhttps.Agentfor axios and readsfs;lib/webhooks/webhooks.jsuses Nodecrypto.@vonage/authimports Nodefsandcryptoat load, JWTs usejsonwebtoken, and@vonage/server-clientreadsprocess.versionon every request.- There is no
dangerouslyAllowBrowser-style check, so client-side use ships the API key. - Plain text sent through a Twilio Messaging Service that has an RCS sender may be upgraded to RCS on Twilio's side; nothing in the library knows about it.
- No RCS connector: Twilio Content API templates (
contentSid,contentVariables) can go out as RCS when the Messaging Service has an RCS sender; the Infobip and Sinch connectors use SMS endpoints only. - The bundled
@vonage/messagesREADME lists RCS as Beta. - No Cloud API provider and no template support; the Twilio receiver detects WhatsApp and
reply()re-adds thewhatsapp:prefix. - The
whatsapp-businessprovider posts tograph.facebook.com(Meta's official API). Novu agents can also reply on WhatsApp. - Notify's Facebook Messenger option (
facebookMessengerPageId) is marked Deprecated inlib/rest/notify/v1/service.d.ts; Conversations is the remaining path. - The provider sends text and
parse_modeonly:blocks,threadIdand media are dropped. The receiver checks the secret token only whensecretTokenis set. - The
telegramid is in@novu/shared2.10.0 and in the framework's provider schemas. The last@novu/providerson npm (2.6.6, Feb 2025) has no Telegram code, so the sending code ships only inside the Novu server. - Blocks and threads are sent; the Slack receiver verifies signatures and parses events, slash commands and interactions.
- No Web API (
chat.postMessage), soproviderMessageIdis alwaysnull. - In
@novu/providers2.6.6,slackposts to the subscriber's incomingwebhookUrl.@novu/shared2.10.0 addsslack_channel/slack_userendpoints and a signing secret, and Slack is also an agent platform for inbound messages. - No bot API, so no DMs;
blocksbecomeembeds. When a handler takes over 2 s the receiver defers the interaction, butreply()posts through the channel webhook, not as the interaction follow-up. - Discord as an agent platform (inbound) is marked
comingSoonin@novu/shared. - Without
blocksit posts a legacyMessageCard, the Office 365 connector format Microsoft is retiring; a Workflows webhook needs an Adaptive Card envelope you pass asblocks. - Wraps the text in an Adaptive Card envelope and treats a legacy connector's HTTP 200 error body as a failure.
- In
@novu/providers2.6.6,msteamsposts to an incomingwebhookUrl.@novu/shared2.10.0 asks for a Microsoft App ID, client secret and tenant ID (Azure Bot / Bot Framework) and addsms_teams_channel/ms_teams_userendpoints. - The Teams resources (Operator Connect, direct routing, meeting bots) are telephony, not Teams messaging.
- The framework's provider override schemas already have a
google-chatkey, but the integration catalog in@novu/shared2.10.0 has no Google Chat provider. It appears only in the list of coming-soon agent platforms. - APNs requires HTTP/2, which Node’s
fetchdoes not speak: it goes overnode:http2. The tokens of FCM and APNs and the encryption of Web Push arenode:crypto. - FCM needs an OAuth access token you obtain yourself; APNs uses
node:http2andnode:crypto(Node-only) and targets the sandbox unlessproduction: true. Web Push is VAPID + RFC 8291 on Web Crypto. - No VAPID Web Push of its own. APNs refuses to send without an HTTP/2-capable
fetchyou inject (Node's is HTTP/1.1) and needs atokenCachein production. - The ids
pusher-beams,appioandpush-webhookalso exist. Web Push works only through FCM'swebPushoverride, Pushpad or OneSignal; no provider sends VAPID Web Push directly. - From reading the code, not running it: the
fcmprovider passes its config asgcmsettings (lib/providers/push/fcm.js). node-pushnotifications 3.1.1 sends Android tokens through its FCM v1 path, which readsfcmsettings that are never set, and itsgcmpath uses Google's legacy API, shut down in 2024. - No Web Push. The package does not mark Notify itself deprecated, only its Messenger and Alexa options; Conversations has separate APN/FCM credentials for its own client SDKs.
mobilePushCredentials(iOS/Android) only attach to SIP connections so Telnyx can wake its own calling SDK; there is no API to send push notifications.- LINE, WeChat, KakaoTalk, Zalo, Mattermost, Rocket.Chat or Matrix after the first release. Email is the sibling library @alexify/mailer; Signal and iMessage have no official API for this.
- Email delegates to a
@visulima/emailinstance; the in-app inbox has memory and unstorage stores. - 10 email providers: SES, Resend, Mailgun, SendGrid, Postmark, MailerSend, Mailtrap, Brevo, SparkPost, Scaleway.
- Also Rocket.Chat, Ryver, GetStream, Grafana OnCall, a generic chat webhook, and tool steps (PagerDuty, Opsgenie, webhook). There is no voice channel.
- Conversations still lists
gbm(Google Business Messages), a channel Google shut down in 2024. - Apple Messages for Business is the Conversation channel
APPLEBC; verification is in@sinch/verification. - 6 SMS, 4 chat, 4 push, in-app, outbound webhook; plus
failover,roundrobin,opentelemetryandmockwrappers. - 10 SMS, 9 chat (WhatsApp and Telegram included), 6 push, 10 email; you can also pass a connector of your own.
- Counted in
@novu/shared2.10.0types/providers.js: 23 email, 38 SMS, 16 chat, 8 push and 1 in-app. These include Novu's own demo and webhook providers.@novu/providers2.6.6, the last version on npm, has 66 implementations. - 7 email, 9 SMS, 4 push, and 1 each for voice (Twilio), web push, Slack and WhatsApp.
- Tries the next provider on any failure (no error classification), each after its own HTTP retries.
- An integration has
primary,priorityandconditionsfields. The published packages show no retry on another provider after an error. - A message moves on when the provider refused it, when the recipient is not on the channel, or when no receipt says it was delivered within a deadline; never after a send of unknown outcome without an idempotency key.
- Moves on only when a send fails: a suppressed or deduplicated send counts as success, and nothing waits for a delivery receipt.
- Only the in-app step returns a result (
seen,read). The SMS, chat, email and push result schemas are empty, so a step cannot react to an SMS or WhatsApp delivery failure. send()sends every channel in the request at once withPromise.all. Nothing falls back from one channel to another.MessageWithFailoverpasses afailoverlist of channel messages to the Messages API; the SDK does not act on it.- Each provider retries its POST 3 times on network errors, timeouts, 408/429 and 5xx (ignoring
Retry-After);retryMiddlewareand the queue worker retry again on top, and no idempotency key reaches the provider, so a timed-out SMS can go out more than once. - The package never retries a trigger. Retries happen in the server's job runner (the execution-details model in
@novu/sharedhas anisRetryfield). - The core retries only
getMe,deleteWebhook(when polling starts) andgetUpdates, never a send. The plugin's defaults are unlimited attempts and unlimited wait, and it also retries network errors, so a lost response can send a message twice. - Two retries by default, on every method. A timed-out
sendMessageis sent again, so a lost response can send a message twice. - Full-jitter exponential backoff, 3 retries by default; 5xx and network errors are never retried, and
maxRetries: 0silently becomes 3 because of||(lib/base/RequestClient.js). - Retries 408/409/429/5xx and timeouts, message POSTs included, but adds no idempotency key by itself; pass
Idempotency-Keyto avoid duplicate sends (client.js). - Only voice API calls are retried: twice on 5xx, immediately, without backoff (
dist/rest/axios.js). - On by default (3 retries, configurable
retryPolicy); 5xx and network errors are not retried (@sinch/sdk-client/dist/client/retry-policy.js). - One bucket per
Notification, shared by every channel and provider. circuitBreakerMiddlewarekeeps a single state per instance, so failures of one SMS provider open it for push, chat and email too.@vonage/server-clientuses an AbortController only whentimeoutis set; callers cannot pass their own AbortSignal.- The worker re-sends the whole message when any one channel fails;
UnstorageQueuereservations never expire, so a crashed worker's job is stuck;BullMqQueuecallsgetNextJob, which a BullMQQueue(as its docs say to pass) does not have. notifme-sdk-queue-rabbitmq4.0.0 was last published in 2017 and gets ~9 downloads a week.- Plivo advertises
media: truebut never sendsmediaUrls; Telegram sends no media. - The SMS step has
bodyonly and the email step has no attachments field. - The Telegram provider drops
blocks, so no inline keyboards; the Slack and Telegram receivers parse button callbacks. - In
@novu/providers2.6.6,whatsapp-businesssends a template only when it receivescustomData.template. The framework has no model for WhatsApp templates. - Liquid, Handlebars and ICU messages need optional peers; rendering is a separate step, not part of
send(). - A multi-recipient SMS counts as sent when any recipient succeeds, and the facade's receipt drops the per-recipient breakdown.
- Bulk is done by the provider: the SDK only joins an array
dstinto Plivo's<-delimited form (dist/resources/messages.js). - No provider reads the payload's
scheduledAt, though Twilio and Telnyx advertisescheduling: true; the SQS adapter caps delays at 15 minutes. - The key never reaches a provider (despite its type doc), is not scoped per channel and is stored only after success, so after a partial failure a retry also skips the channel that failed.
- The package sends
transactionIdand uses it to cancel a trigger. The package does not show whether the server rejects a duplicate. - Only a few other APIs (Conversations v2, call Payments, user-defined messages) accept an idempotency key.
NotificationEvent(queued … read) is shared, but Standard Webhooks maps unknown types todelivered, the Slack and SNS parsers always returndelivered, and nothing links events to sends.getMessageIdandparseEventBodyfrom Novu are not implemented (MIGRATION.md).@novu/shareddefines events such asmessage.sent,message.delivered,message.failedandmessage.readfor Novu's outbound webhooks.- Conversation delivery events share one model across its channels; SMS delivery reports are a separate model.
- Twilio
statusCallback, InfobipnotifyUrl, TelnyxwebhookUrl, SinchcallbackUrlare passed through. - In
@novu/providers2.6.6,parseEventBodyexists for 8 email providers and 5 SMS providers (Twilio, Plivo, Telnyx, Termii, RingCentral). JSON.parseplus a TypeScript cast, with no runtime schema check (lib/webhooks.js).- Twilio verification needs the public URL passed in; Telegram's secret token is optional.
- The bridge checks a
novu-signatureHMAC with a 5-minute window, but only whenNODE_ENVis set to something other thandevelopment/dev, or whenstrictAuthenticationis on. The provider code verifies no signatures on status webhooks. - The check is optional: when no
secretTokenis set,webhookCallbackaccepts every request. Telegram sends a shared secret header and does not sign payloads. webhookCallbackthrows at setup unless you setsecretTokenor passallowUnauthenticated: true. The comparison runs in constant time.- Timing-safe compare, tries the URL with and without port, and
validateRequestWithBodychecksbodySHA256for JSON bodies (lib/webhooks/webhooks.js). The scheme has no timestamp, so replays are not rejected. @vonage/jwtverifySignaturechecks the JWT but not itspayload_hashagainst the body and is not re-exported by@vonage/server-sdk; SMSverifySignaturecompares with===, not timing-safe.- Compares with
===(not timing-safe) and does not track nonces against replay (dist/utils/v3Security.js). validateWebhookSignaturecompares with===(not timing-safe), never checks the timestamp's age, and re-serializes object bodies withJSON.stringify(@sinch/sdk-client/dist/utils/authorization.helper.js).- The conversational providers are Slack, Telegram, WhatsApp Business, MS Teams, Sendblue and Novu email (
@novu/sharedconversational-providers.js). - A suppressed send is reported as
successful: true, soroute()stops there and does not try the next channel. - Duck-typed: pass a tracer and meter;
@opentelemetry/apiis never imported. - The only way to hook outgoing API calls is to inject your own
fetch. - A channel with no providers configured silently falls back to the logger provider. It logs the whole request (recipient included) to the console and reports success with a random id.
- Twilio test credentials work on the provider side; a custom
httpClientcan stub requests. dist/rest/client-test.jsis the SDK's own fixture client with hard-coded responses, not a documented test mode.NotificationEventBusandMemoryEventStoreexist, but the library never emits an event to them.- bullmq, pg-boss,
@aws-sdk/client-sqs, unstorage, liquidjs, handlebars, croner, intl-messageformat,@opentelemetry/api,@visulima/email,@visulima/workflow. - There are also 13 optional peer dependencies (express, next, hono, zod, ai, langchain…).
- Also
https-proxy-agent@5andxmlbuilder(TwiML). wsis needed only for the speech WebSocket helpers.- Third-party packages arrive transitively:
debug,jsonwebtoken,uuid. requestbacks only the bundled test client; runtime deps also include@types/node@14and thequerystringshim. Importing the client overwrites the globalaxios.defaults.httpAgent/httpsAgent(dist/rest/axios.js).- Sub-packages bring
node-fetch@2.7.0andform-data;@sinch/sdk-clientrequires@sinch/sdk-core/package.jsonwithout declaring it. - Most downloads are of 0.66/0.67. Version 2.1.0 gets ~8.7k a week (npm per-version API).
- The README lists the Vonage id as
nexmo, but onlyvonageis accepted; MIGRATION.md says the alias was removed in 1.0. - Version 1.0.0 came out on 2026-06-12 and 2.0.0 on 2026-08-16. The README says v2 is a from-scratch redesign with no v1 compatibility.
- Ships several releases a month (7.25 → 7.28 between 2026-10-06 and 2026-10-22).
How the table was made
- From the published code, not the README. Every cell was decided by reading the package as published on npm (
npm pack), with itspackage.jsonand the registry's metadata, as of 2026-10-22. The repositories were not read, so the tests and the CI of a project are not in the table. - Defects were not reproduced. A cell that names a bug is a finding from reading the code; reproduce it before relying on it.
- Runtimes were not run. Deno, Bun and Workers support is inferred from imports,
exportsmaps and the metadata of the package. - Downloads are npm's count for the week of 2026-10-12 to 2026-10-18.
- Novu is judged as what an application gets:
@novu/frameworkdefines the workflows, and the Novu server (Novu Cloud or self-hosted) runs them, with the providers, retries and preferences. A feature of the server is marked as such. - Design references outside Node.js, not in the table: Symfony Notifier (PHP; texters and chatters behind DSNs,
||for failover, a channel policy by importance, and a bridge for TurboSMS), Apprise (Python; some 200 services by URL), and noticed (Ruby; a notification per recipient, and a delivery that waits to see if the first was read).