Skip to content

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 as sent; 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's failover, Sinch's channel_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 sendMessage after a timeout, and grammY's auto-retry plugin retries network errors without a limit. Only Telnyx takes an Idempotency-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 Queue does 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 unless NODE_ENV says 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 MessageCard of 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 fetch injected 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 request among 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
WhatsApp🔜 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.Chat39email, in-app, LINE, iMessage, Webex, Mattermost, Zulip…40email, 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 channel16 + email via @visulima/email4335 across SMS, chat, push, email4486 provider ids (38 SMS)4524 plus custom461 (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 dependencies00 (11 optional peers)101011 (ajv, liquidjs, sanitize-html, chat…)1028 (winston, nodemailer, node-fetch 2, web-push…)4 (node-fetch 2, debug, abort-controller, types)06: axios, jsonwebtoken, qs, dayjs, …1030 (optional peer ws)10417 (all @vonage/*)10515, incl. deprecated request1069 (all @sinch/*)107
Module formatCJS, ESM importESM onlyESM + CJSESM + CJSCJS only (Babel)CJS for Node, ESM web bundleESM + CJSCJSboth (CJS + ESM)both (CJS + ESM)CJSCJS
LicenseMITMITMITISCMITMITMITMITMITApache-2.0MITApache-2.0
Version (2026-10-22)0.0.1, a placeholder2.0.01.2.02.14.01.16.251.46.02.1.0 (2.2.0-rc2 on next)6.2.07.28.03.30.14.79.01.7.0
Last releasenot published yet2026-10-152026-10-182026-09-282025-03-252026-08-262026-08-242026-10-122026-10-222026-09-092026-07-282026-10-15
npm downloads / week—14k49443k7052.5M125k1083.7M238k73k23k11k
Owner / maintenance risk1 author, Alexis Technologies1 author (Anolilab), 52 versions since June1 author, 5 versions since June 2026109company (Novu), active2 maintainers, no release since Mar 20251 npm maintainer, active org2 maintainers, two rewrites in 2026110Twilio (company), activeTelnyx (company), Stainless-generated111Vonage (company), activePlivo (company), activeSinch (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.

  1. engines: ^22.14.0 || >=24.10.0.
  2. No engines field; the bundled README lists Node.js 20 LTS or later.
  3. Through npm:@alexify/messenger.
  4. The 2.0.0 major release exists only to remove Deno as a supported runtime (CHANGELOG).
  5. package.json maps a long list of runtime conditions (deno, bun, workerd, …) to the same build; the README only mentions Node.js.
  6. Every SMS and chat provider and every webhook verifier uses only fetch and Web Crypto; APNs (node:http2) and the BullMQ / pg-boss / SQS queue adapters are Node-only.
  7. The Sms and Push facades statically import the SNS, FCM and APNs connectors, which use Node's crypto and Buffer; the chat connectors use only fetch.
  8. The main entry statically imports path, process, url and async_hooks and reads process.env and process.hrtime, so a Worker needs nodejs_compat. Nothing in the package declares edge support. Sending itself runs on the Novu server.
  9. lib/base/RequestClient.js builds an https.Agent for axios and reads fs; lib/webhooks/webhooks.js uses Node crypto.
  10. @vonage/auth imports Node fs and crypto at load, JWTs use jsonwebtoken, and @vonage/server-client reads process.version on every request.
  11. There is no dangerouslyAllowBrowser-style check, so client-side use ships the API key.
  12. 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.
  13. 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.
  14. The bundled @vonage/messages README lists RCS as Beta.
  15. No Cloud API provider and no template support; the Twilio receiver detects WhatsApp and reply() re-adds the whatsapp: prefix.
  16. The whatsapp-business provider posts to graph.facebook.com (Meta's official API). Novu agents can also reply on WhatsApp.
  17. Notify's Facebook Messenger option (facebookMessengerPageId) is marked Deprecated in lib/rest/notify/v1/service.d.ts; Conversations is the remaining path.
  18. The provider sends text and parse_mode only: blocks, threadId and media are dropped. The receiver checks the secret token only when secretToken is set.
  19. The telegram id is in @novu/shared 2.10.0 and in the framework's provider schemas. The last @novu/providers on npm (2.6.6, Feb 2025) has no Telegram code, so the sending code ships only inside the Novu server.
  20. Blocks and threads are sent; the Slack receiver verifies signatures and parses events, slash commands and interactions.
  21. No Web API (chat.postMessage), so providerMessageId is always null.
  22. In @novu/providers 2.6.6, slack posts to the subscriber's incoming webhookUrl. @novu/shared 2.10.0 adds slack_channel/slack_user endpoints and a signing secret, and Slack is also an agent platform for inbound messages.
  23. No bot API, so no DMs; blocks become embeds. When a handler takes over 2 s the receiver defers the interaction, but reply() posts through the channel webhook, not as the interaction follow-up.
  24. Discord as an agent platform (inbound) is marked comingSoon in @novu/shared.
  25. Without blocks it posts a legacy MessageCard, the Office 365 connector format Microsoft is retiring; a Workflows webhook needs an Adaptive Card envelope you pass as blocks.
  26. Wraps the text in an Adaptive Card envelope and treats a legacy connector's HTTP 200 error body as a failure.
  27. In @novu/providers 2.6.6, msteams posts to an incoming webhookUrl. @novu/shared 2.10.0 asks for a Microsoft App ID, client secret and tenant ID (Azure Bot / Bot Framework) and adds ms_teams_channel/ms_teams_user endpoints.
  28. The Teams resources (Operator Connect, direct routing, meeting bots) are telephony, not Teams messaging.
  29. The framework's provider override schemas already have a google-chat key, but the integration catalog in @novu/shared 2.10.0 has no Google Chat provider. It appears only in the list of coming-soon agent platforms.
  30. APNs requires HTTP/2, which Node’s fetch does not speak: it goes over node:http2. The tokens of FCM and APNs and the encryption of Web Push are node:crypto.
  31. FCM needs an OAuth access token you obtain yourself; APNs uses node:http2 and node:crypto (Node-only) and targets the sandbox unless production: true. Web Push is VAPID + RFC 8291 on Web Crypto.
  32. No VAPID Web Push of its own. APNs refuses to send without an HTTP/2-capable fetch you inject (Node's is HTTP/1.1) and needs a tokenCache in production.
  33. The ids pusher-beams, appio and push-webhook also exist. Web Push works only through FCM's webPush override, Pushpad or OneSignal; no provider sends VAPID Web Push directly.
  34. From reading the code, not running it: the fcm provider passes its config as gcm settings (lib/providers/push/fcm.js). node-pushnotifications 3.1.1 sends Android tokens through its FCM v1 path, which reads fcm settings that are never set, and its gcm path uses Google's legacy API, shut down in 2024.
  35. 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.
  36. mobilePushCredentials (iOS/Android) only attach to SIP connections so Telnyx can wake its own calling SDK; there is no API to send push notifications.
  37. 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.
  38. Email delegates to a @visulima/email instance; the in-app inbox has memory and unstorage stores.
  39. 10 email providers: SES, Resend, Mailgun, SendGrid, Postmark, MailerSend, Mailtrap, Brevo, SparkPost, Scaleway.
  40. Also Rocket.Chat, Ryver, GetStream, Grafana OnCall, a generic chat webhook, and tool steps (PagerDuty, Opsgenie, webhook). There is no voice channel.
  41. Conversations still lists gbm (Google Business Messages), a channel Google shut down in 2024.
  42. Apple Messages for Business is the Conversation channel APPLEBC; verification is in @sinch/verification.
  43. 6 SMS, 4 chat, 4 push, in-app, outbound webhook; plus failover, roundrobin, opentelemetry and mock wrappers.
  44. 10 SMS, 9 chat (WhatsApp and Telegram included), 6 push, 10 email; you can also pass a connector of your own.
  45. Counted in @novu/shared 2.10.0 types/providers.js: 23 email, 38 SMS, 16 chat, 8 push and 1 in-app. These include Novu's own demo and webhook providers. @novu/providers 2.6.6, the last version on npm, has 66 implementations.
  46. 7 email, 9 SMS, 4 push, and 1 each for voice (Twilio), web push, Slack and WhatsApp.
  47. Tries the next provider on any failure (no error classification), each after its own HTTP retries.
  48. An integration has primary, priority and conditions fields. The published packages show no retry on another provider after an error.
  49. 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.
  50. Moves on only when a send fails: a suppressed or deduplicated send counts as success, and nothing waits for a delivery receipt.
  51. 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.
  52. send() sends every channel in the request at once with Promise.all. Nothing falls back from one channel to another.
  53. MessageWithFailover passes a failover list of channel messages to the Messages API; the SDK does not act on it.
  54. Each provider retries its POST 3 times on network errors, timeouts, 408/429 and 5xx (ignoring Retry-After); retryMiddleware and 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.
  55. The package never retries a trigger. Retries happen in the server's job runner (the execution-details model in @novu/shared has an isRetry field).
  56. The core retries only getMe, deleteWebhook (when polling starts) and getUpdates, 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.
  57. Two retries by default, on every method. A timed-out sendMessage is sent again, so a lost response can send a message twice.
  58. Full-jitter exponential backoff, 3 retries by default; 5xx and network errors are never retried, and maxRetries: 0 silently becomes 3 because of || (lib/base/RequestClient.js).
  59. Retries 408/409/429/5xx and timeouts, message POSTs included, but adds no idempotency key by itself; pass Idempotency-Key to avoid duplicate sends (client.js).
  60. Only voice API calls are retried: twice on 5xx, immediately, without backoff (dist/rest/axios.js).
  61. On by default (3 retries, configurable retryPolicy); 5xx and network errors are not retried (@sinch/sdk-client/dist/client/retry-policy.js).
  62. One bucket per Notification, shared by every channel and provider.
  63. circuitBreakerMiddleware keeps a single state per instance, so failures of one SMS provider open it for push, chat and email too.
  64. @vonage/server-client uses an AbortController only when timeout is set; callers cannot pass their own AbortSignal.
  65. The worker re-sends the whole message when any one channel fails; UnstorageQueue reservations never expire, so a crashed worker's job is stuck; BullMqQueue calls getNextJob, which a BullMQ Queue (as its docs say to pass) does not have.
  66. notifme-sdk-queue-rabbitmq 4.0.0 was last published in 2017 and gets ~9 downloads a week.
  67. Plivo advertises media: true but never sends mediaUrls; Telegram sends no media.
  68. The SMS step has body only and the email step has no attachments field.
  69. The Telegram provider drops blocks, so no inline keyboards; the Slack and Telegram receivers parse button callbacks.
  70. In @novu/providers 2.6.6, whatsapp-business sends a template only when it receives customData.template. The framework has no model for WhatsApp templates.
  71. Liquid, Handlebars and ICU messages need optional peers; rendering is a separate step, not part of send().
  72. A multi-recipient SMS counts as sent when any recipient succeeds, and the facade's receipt drops the per-recipient breakdown.
  73. Bulk is done by the provider: the SDK only joins an array dst into Plivo's <-delimited form (dist/resources/messages.js).
  74. No provider reads the payload's scheduledAt, though Twilio and Telnyx advertise scheduling: true; the SQS adapter caps delays at 15 minutes.
  75. 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.
  76. The package sends transactionId and uses it to cancel a trigger. The package does not show whether the server rejects a duplicate.
  77. Only a few other APIs (Conversations v2, call Payments, user-defined messages) accept an idempotency key.
  78. NotificationEvent (queued … read) is shared, but Standard Webhooks maps unknown types to delivered, the Slack and SNS parsers always return delivered, and nothing links events to sends.
  79. getMessageId and parseEventBody from Novu are not implemented (MIGRATION.md).
  80. @novu/shared defines events such as message.sent, message.delivered, message.failed and message.read for Novu's outbound webhooks.
  81. Conversation delivery events share one model across its channels; SMS delivery reports are a separate model.
  82. Twilio statusCallback, Infobip notifyUrl, Telnyx webhookUrl, Sinch callbackUrl are passed through.
  83. In @novu/providers 2.6.6, parseEventBody exists for 8 email providers and 5 SMS providers (Twilio, Plivo, Telnyx, Termii, RingCentral).
  84. JSON.parse plus a TypeScript cast, with no runtime schema check (lib/webhooks.js).
  85. Twilio verification needs the public URL passed in; Telegram's secret token is optional.
  86. The bridge checks a novu-signature HMAC with a 5-minute window, but only when NODE_ENV is set to something other than development/dev, or when strictAuthentication is on. The provider code verifies no signatures on status webhooks.
  87. The check is optional: when no secretToken is set, webhookCallback accepts every request. Telegram sends a shared secret header and does not sign payloads.
  88. webhookCallback throws at setup unless you set secretToken or pass allowUnauthenticated: true. The comparison runs in constant time.
  89. Timing-safe compare, tries the URL with and without port, and validateRequestWithBody checks bodySHA256 for JSON bodies (lib/webhooks/webhooks.js). The scheme has no timestamp, so replays are not rejected.
  90. @vonage/jwt verifySignature checks the JWT but not its payload_hash against the body and is not re-exported by @vonage/server-sdk; SMS verifySignature compares with ===, not timing-safe.
  91. Compares with === (not timing-safe) and does not track nonces against replay (dist/utils/v3Security.js).
  92. validateWebhookSignature compares with === (not timing-safe), never checks the timestamp's age, and re-serializes object bodies with JSON.stringify (@sinch/sdk-client/dist/utils/authorization.helper.js).
  93. The conversational providers are Slack, Telegram, WhatsApp Business, MS Teams, Sendblue and Novu email (@novu/shared conversational-providers.js).
  94. A suppressed send is reported as successful: true, so route() stops there and does not try the next channel.
  95. Duck-typed: pass a tracer and meter; @opentelemetry/api is never imported.
  96. The only way to hook outgoing API calls is to inject your own fetch.
  97. 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.
  98. Twilio test credentials work on the provider side; a custom httpClient can stub requests.
  99. dist/rest/client-test.js is the SDK's own fixture client with hard-coded responses, not a documented test mode.
  100. NotificationEventBus and MemoryEventStore exist, but the library never emits an event to them.
  101. bullmq, pg-boss, @aws-sdk/client-sqs, unstorage, liquidjs, handlebars, croner, intl-messageformat, @opentelemetry/api, @visulima/email, @visulima/workflow.
  102. There are also 13 optional peer dependencies (express, next, hono, zod, ai, langchain…).
  103. Also https-proxy-agent@5 and xmlbuilder (TwiML).
  104. ws is needed only for the speech WebSocket helpers.
  105. Third-party packages arrive transitively: debug, jsonwebtoken, uuid.
  106. request backs only the bundled test client; runtime deps also include @types/node@14 and the querystring shim. Importing the client overwrites the global axios.defaults.httpAgent/httpsAgent (dist/rest/axios.js).
  107. Sub-packages bring node-fetch@2.7.0 and form-data; @sinch/sdk-client requires @sinch/sdk-core/package.json without declaring it.
  108. Most downloads are of 0.66/0.67. Version 2.1.0 gets ~8.7k a week (npm per-version API).
  109. The README lists the Vonage id as nexmo, but only vonage is accepted; MIGRATION.md says the alias was removed in 1.0.
  110. 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.
  111. 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 its package.json and 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, exports maps 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/framework defines 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).

Released under the MIT License.