Novu is the open-source communication infrastructure for products and AI agents. It gives you one API and one unified conversation model to reach your users on every channel they live on — an embeddable in-app Inbox, Email, SMS, Push, and Chat (Slack, Microsoft Teams, Telegram, WhatsApp, Discord, and more).
Instead of integrating a separate provider for every channel, writing your own notification feed, and maintaining a webhook handler per platform, you integrate Novu once. You define workflows (the logic and templates behind a kind of notification), trigger them with a single API call, and Novu routes each message to the right channel for each user — with batching (digest), delays, scheduling, per-user preferences, and a real-time <Inbox /> component out of the box.
This guide covers everything you need to use Novu as a developer: getting started on Novu Cloud or self-hosted, sending your first notification, building workflows in code, embedding the Inbox, connecting agents, configuring providers, and troubleshooting. For deployment, architecture, configuration internals, and security hardening, see the companion Novu Admin Guide.
Note
Novu is distributed as a monorepo of apps (API, worker, websocket service, dashboard) and npm packages (
@novu/js,@novu/react,@novu/nextjs,@novu/react-native,@novu/framework,@novu/api, and thenovuCLI). This guide documents the open-source platform at commitb6ab172. The always-current API reference and channel-specific guides live at docs.novu.co.
Table of Contents
- Quick Start
- Core Concepts
- Installation & Setup
- Sending Your First Notification
- Building Workflows in Code (
@novu/framework) - The
novuCLI Reference - Embedding the Inbox
- Subscribers & Preferences
- Digest, Delay & Scheduling
- Channels & Providers
- Agent Communication Infrastructure (ACI)
- Translations & Layouts
- Environments & Promotion
- Common Tasks (Recipes)
- Troubleshooting
- FAQ
- Reference
1. Quick Start
There are two ways to run Novu, and the developer experience is the same for both:
- Novu Cloud — a hosted account at dashboard.novu.co. Fastest way to start; nothing to deploy.
- Self-hosted — run the full stack with Docker on your own infrastructure (see Installation & Setup and the Admin Guide).
1.1 The fastest path: scaffold a starter app
If you have Node.js installed, you can scaffold a working Novu app — including a Next.js page, a workflow, and an embedded Inbox — with one command:
npx novu@latest init
This walks you through creating an account (or signing in), generates a project from a template, and prints the next steps. Then start the local Dev Studio so notifications you trigger locally show up instantly:
npx novu@latest dev --port 4000
✔ Novu Dev Studio running
Local Bridge tunnel established
Studio: http://localhost:2022
Tip
The
npx novu@latest devcommand opens a local Bridge tunnel and a Studio UI so you can build and test workflows on your machine before promoting them to your cloud or self-hosted environment. Pass--no-studioto skip the UI, or--headlessto avoid opening a browser.
1.2 The three things every Novu app does
Every Novu integration boils down to three steps:
- Define a workflow — the container for a notification's steps and templates (in the dashboard's no-code editor, or in code with
@novu/framework). - Trigger the workflow — make one API call (
POST /v1/events/trigger) from your backend with aworkflowId, atosubscriber, and apayload. - Deliver & display — Novu runs the workflow, sends each channel's message through the configured provider, and (for in-app) streams it to the
<Inbox />component in real time.
your backend ──trigger──▶ Novu API ──▶ workflow engine ──▶ Email provider
├──▶ SMS provider
├──▶ Push provider
├──▶ Chat provider
└──▶ In-App Inbox (websocket ▶ <Inbox/>)
Important
A notification is never sent directly to a channel. It is always sent by triggering a workflow, which decides which channels fire, in what order, and under what conditions. This indirection is what lets you change delivery logic without redeploying your app.
2. Core Concepts
Understanding these six concepts is enough to use everything in Novu.
| Concept | What it is |
|---|---|
| Workflow | A blueprint for a kind of notification. Has a unique trigger identifier (workflowId), a name, and an ordered list of steps. |
| Trigger | The event that starts a workflow — a call to the Novu API with a workflowId, a recipient, and a payload of data. |
| Step | A single action in a workflow: a channel step (email, sms, push, chat, inApp) or a flow-control step (digest, delay, custom). |
| Subscriber | A recipient of notifications, identified by your own subscriberId. Holds channel credentials (email, phone, device tokens) and preferences. |
| Channel | A delivery medium: In-App (Inbox), Email, SMS, Push, or Chat. |
| Provider | The concrete service that delivers a channel's message — e.g. SendGrid or Resend for Email, Twilio for SMS, FCM for Push, Slack for Chat. |
Note
A subscriber is your user as Novu sees them. You usually create or update subscribers on the fly when you trigger a workflow (Novu upserts them), or sync them ahead of time. The
subscriberIdis the stable key you control — use your own user ID.
2.1 The unified conversation model
Beyond one-way notifications, Novu models conversations: inbound messages from a channel (a Slack reply, an email response, a Telegram message) are normalized into one consistent shape and can be routed back to your code or to an agent. This is the foundation of both the product notification features and the Agent Communication Infrastructure (see Section 11).
3. Installation & Setup
3.1 Choosing how to run Novu
Novu Cloud (hosted)
- Create a free account at dashboard.novu.co.
- Copy your Secret Key (a.k.a. API key) and Application Identifier from Settings → API Keys.
- Use
https://api.novu.coas your API base URL (orhttps://eu.api.novu.cofor the EU region).
Self-hosted (Docker)
Run the one-line community installer on a host with Docker, curl, and openssl:
curl -fsSL https://raw.githubusercontent.com/novuhq/novu/next/docker/community/setup.sh | bash
The script downloads docker-compose.yml and .env.example, generates secure random secrets, and starts the stack:
▸ Novu is running:
Dashboard : http://localhost:4000
API : http://localhost:3000
WebSocket : http://localhost:3002
See the Admin Guide for production hardening, full environment-variable reference, and scaling.
Warning
The Secret Key is a server-side credential. Never ship it to the browser or commit it to source control. The browser only ever needs your Application Identifier (a public value) and the subscriber's identity.
3.2 Installing the SDKs
Install only the packages you need:
| Package | Use it for | Install |
|---|---|---|
@novu/api |
Server-side: trigger workflows, manage subscribers, topics, integrations | npm i @novu/api |
@novu/react |
React apps: the <Inbox /> component and hooks |
npm i @novu/react |
@novu/nextjs |
Next.js apps: SSR-friendly Inbox wrapper | npm i @novu/nextjs |
@novu/js |
Framework-agnostic browser/JS client for the Inbox | npm i @novu/js |
@novu/react-native |
React Native apps: notification hooks | npm i @novu/react-native |
@novu/framework |
Code-first workflow definitions served from your app | npm i @novu/framework |
novu (CLI) |
Scaffolding, local dev, sync, agent connect | npx novu@latest <command> |
Tip
Requirements: Node.js 22 is required to build or self-host the platform itself (
"node": ">=22 <23"). The published client SDKs support current LTS Node and modern browsers; React packages target React 19.
4. Sending Your First Notification
After you have a workflow with a trigger identifier (create one in the dashboard, or in code — see Section 5), trigger it from your backend.
4.1 Trigger with the server SDK
import { Novu } from '@novu/api';
const novu = new Novu({ secretKey: process.env.NOVU_SECRET_KEY });
await novu.trigger({
workflowId: 'welcome-onboarding-email',
to: {
subscriberId: 'user_123',
email: '[email removed]',
},
payload: {
firstName: 'Jane',
organizationName: 'Acme',
},
});
{ acknowledged: true, status: 'processed', transactionId: '...' }
workflowId— the workflow's trigger identifier.to— the recipient. Pass asubscriberId(Novu upserts the subscriber) plus any channel credentials it needs (email,phone, etc.). You can also pass an array to trigger many recipients, or a topic key for broadcast.payload— arbitrary JSON your templates reference (e.g.{{firstName}}).
4.2 Trigger with a raw HTTP request
The SDK is a thin wrapper over the REST API. Any HTTP client works:
curl -X POST https://api.novu.co/v1/events/trigger \
-H "Authorization: ApiKey $NOVU_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "welcome-onboarding-email",
"to": { "subscriberId": "user_123", "email": "[email removed]" },
"payload": { "firstName": "Jane" }
}'
Important
Novu authenticates server requests with the header
Authorization: ApiKey <YOUR_SECRET_KEY>. This exact scheme is used by the SDKs and the CLI. (Self-hosted users substitute their own API base URL.)
4.3 Verifying delivery
- In the dashboard → Activity Feed, every trigger appears with its per-step execution status (sent, failed, delayed, digested).
- For in-app notifications, they appear in the
<Inbox />instantly via the websocket service. - Failed steps show the provider error message and are retried by the worker according to the channel's retry policy.
5. Building Workflows in Code (@novu/framework)
You can author workflows as code with @novu/framework (the "Bridge" / code-first approach). Your workflows live in your repository, are type-safe, and are served from an endpoint Novu calls during execution. This keeps templates, conditions, and payload schemas versioned alongside your app.
5.1 Define a workflow
import { workflow } from '@novu/framework';
import { z } from 'zod';
export const welcomeWorkflow = workflow(
'welcome-onboarding-email',
async ({ step, payload }) => {
await step.email('send-email', async () => ({
subject: `Welcome, ${payload.firstName}!`,
body: `Thanks for joining ${payload.organizationName}.`,
}));
await step.inApp('notify-inbox', async () => ({
body: `Welcome aboard, ${payload.firstName} 🎉`,
}));
},
{
payloadSchema: z.object({
firstName: z.string(),
organizationName: z.string().default('our app'),
}),
}
);
- The first argument is the trigger identifier — the same
workflowIdyou pass when triggering. - Each
step.<channel>()call is a step. The callback returns that channel's content. - A
payloadSchema(Zod or JSON Schema) validates the trigger payload and generates a test-data form in the dashboard.
Note
Step types map to channels:
step.email,step.sms,step.push,step.chat,step.inApp, plus flow-control stepsstep.digest,step.delay, andstep.custom. The exact options for each step are documented at docs.novu.co/workflows.
5.2 Serve your workflows
Expose your workflows from a framework endpoint (Next.js, Express, Remix, SvelteKit, H3, Nuxt, NestJS, and AWS Lambda servers are supported by @novu/framework):
// app/api/novu/route.ts (Next.js App Router)
import { serve } from '@novu/framework/next';
import { welcomeWorkflow } from '../../../novu/workflows';
export const { GET, POST, OPTIONS } = serve({
workflows: [welcomeWorkflow],
});
5.3 Test locally, then sync
While developing, run the Dev Studio so Novu can reach your local endpoint through a tunnel:
npx novu@latest dev --port 4000 --route /api/novu
When you are ready to push your code-defined workflows to your cloud or self-hosted environment, run sync:
npx novu@latest sync \
--bridge-url https://your-app.com/api/novu \
--secret-key $NOVU_SECRET_KEY \
--api-url https://api.novu.co
Under the hood, sync sends your Bridge URL to POST /v1/bridge/sync?source=cli with an Authorization: ApiKey <secret-key> header; Novu discovers your workflows (and any agents) from the Bridge and registers them.
Tip
Add
novu syncto your CI/CD pipeline so every deploy registers the latest workflows. The CLI auto-detects common CI environments (GitHub Actions, GitLab CI, CircleCI, Travis, Jenkins, Buildkite, Drone) and switches to non-interactive logging.
6. The novu CLI Reference
Run any command with npx novu@latest <command>. The CLI is the novu package.
| Command | Purpose |
|---|---|
novu init |
Scaffold a starter app from a template. |
novu dev |
Start the local Dev Studio + Bridge tunnel for local workflow development. |
novu sync |
Register your code-defined workflows (Bridge URL) with a Novu environment. |
novu connect |
Connect an existing AI agent to a channel (Slack, Telegram, Email) — see ACI. |
novu wizard |
Interactive setup wizard (full |
novu translations pull / push |
Download or upload translation files for localized content. |
novu step publish |
Bundle and deploy code-defined step handlers. |
6.1 novu dev
novu dev [options]
| Option | Description |
|---|---|
-p, --port <port> |
The local Bridge endpoint port. |
-r, --route <route> |
The Bridge endpoint route (e.g. /api/novu). |
-o, --origin <origin> |
The Bridge endpoint origin. |
-d, --dashboard-url <url> |
The Novu Cloud Dashboard URL. |
-sp, --studio-port <port> |
The local Studio server port. |
-sh, --studio-host <host> |
The local Studio server host. |
-t, --tunnel <url> |
Use a self-hosted tunnel (e.g. an ngrok URL). |
-H, --headless |
Run without opening the browser. |
--no-studio |
Skip starting the local Studio server. |
--run <command> |
Spawn a local app server before opening the tunnel. |
6.2 novu sync
novu sync [options]
| Option | Description |
|---|---|
--bridge-url <url> |
Your deployed Bridge endpoint URL. Required. |
-s, --secret-key <key> |
Your Novu Secret Key. Required. |
-a, --api-url <url> |
The Novu API URL (defaults to Novu Cloud). |
Caution
novu syncfails fast if--bridge-urlor--secret-keyis missing, printingA bridge URL is required for the sync command…orA secret key is required for the sync command…. Supply both.
6.3 novu init
| Option | Description |
|---|---|
-a, --api-url <url> |
The Novu Cloud API URL. |
-t, --template <name> |
The template to use (notifications or agent). |
--agent-identifier <id> |
Agent identifier to use in a scaffolded agent template. |
6.4 novu translations
# Download translation files from a Novu environment
npx novu@latest translations pull --secret-key $KEY --directory ./translations
# Upload local translation files
npx novu@latest translations push --secret-key $KEY --directory ./translations
Note
Set
NOVU_CLI_DEBUG=trueto see verbose CLI request logging. CLI analytics can be disabled withANALYTICS_ENABLED=false.
7. Embedding the Inbox
The Inbox is a real-time, embeddable notification center for your app's in-app channel. You drop in one component and get a bell, an unread count, a notification list, actions, and a preferences panel — all powered by the In-App channel and the websocket service.
7.1 React
import { Inbox } from '@novu/react';
export function NotificationsBell() {
return (
<Inbox
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
subscriberId="user_123"
/>
);
}
applicationIdentifier— your public Application Identifier.subscriberId— the current user's identifier (the same one you trigger to).
7.2 Next.js
'use client';
import { Inbox } from '@novu/nextjs';
export function Bell() {
return <Inbox applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!} subscriberId="user_123" />;
}
7.3 Self-hosted: point the Inbox at your instance
When self-hosting, tell the Inbox where your API and websocket services live:
<Inbox
applicationIdentifier="<your-app-id>"
subscriberId="user_123"
backendUrl="https://novu-api.yourcompany.com"
socketUrl="https://novu-ws.yourcompany.com"
/>
Tip
The Inbox is fully themeable and supports custom rendering of notification items, tabs/filters, and a built-in preferences view so users manage their own channel settings without you building UI. See docs.novu.co/platform/inbox.
7.4 Securing the Inbox with HMAC
For production, enable HMAC subscriber verification so a user can only read their own feed. You compute an HMAC hash of the subscriberId on your server (using your Secret Key) and pass it to the Inbox as subscriberHash. Enable HMAC for the In-App integration in the dashboard first.
Warning
Without HMAC verification, a
subscriberIdis a guessable identifier. Always enable HMAC in production so notifications cannot be read by spoofing another user's ID.
8. Subscribers & Preferences
8.1 Managing subscribers
Subscribers are upserted automatically when you trigger with a to object, but you can also manage them explicitly:
import { Novu } from '@novu/api';
const novu = new Novu({ secretKey: process.env.NOVU_SECRET_KEY });
await novu.subscribers.create({
subscriberId: 'user_123',
email: '[email removed]',
phone: '+15555550123',
firstName: 'Jane',
locale: 'en_US',
});
Channel credentials (push device tokens, chat webhook URLs, Slack access tokens) are attached per subscriber so the right provider can reach them.
8.2 The three levels of preferences
Novu resolves whether a given channel fires for a given subscriber by combining three layers:
| Level | Set by | Scope |
|---|---|---|
| Workflow channel preferences | You (workflow author) | Defaults and which channels are user-editable. |
| Subscriber preferences per workflow | The end user | "Mute SMS for the weekly-digest workflow." |
| Subscriber global preferences | The end user | "Turn off all SMS everywhere." |
Users manage levels 2 and 3 directly inside the <Inbox /> preferences panel, or you build your own UI on the preferences API.
Note
A critical workflow can be marked so that it ignores subscriber preferences (e.g. security alerts that must always send). Configure this on the workflow.
9. Digest, Delay & Scheduling
These flow-control steps shape when and how often messages go out.
9.1 Digest
The digest engine collects multiple trigger events for the same subscriber over a window, aggregates them, and sends a single combined message.
Example: a user gets 100 events in a few minutes, but you only want to notify them once per hour.
await step.digest('batch', async () => ({
amount: 1,
unit: 'hours', // batch events for one hour
}));
await step.inApp('summary', async (controls, { events }) => ({
body: `You have ${events.length} new updates`,
}));
9.2 Delay / Schedule
The delay step waits a specified amount of time before continuing to the next steps. Common uses:
- Send a follow-up email 24 hours after registration.
- Trigger a reminder if the user hasn't completed an action.
- Give the user a grace period to cancel an action.
- Schedule a notification for a specific date.
await step.delay('wait-a-day', async () => ({
amount: 24,
unit: 'hours',
}));
Tip
Delays and digests are durable: the worker persists the job and resumes it when the timer fires, so they survive restarts. Long delays don't hold a connection open.
10. Channels & Providers
Novu speaks to each channel through a provider. You configure providers (and their credentials) once as integrations in the dashboard; workflows then deliver through whatever provider is active for each channel and environment.
10.1 Supported providers
<details> <summary><strong>Email (19 providers)</strong></summary>
Amazon SES · Braze · Brevo · Custom SMTP (Nodemailer) · Email Webhook · Email.js · Infobip · MailerSend · Mailgun · Mailjet · Mailtrap · Mandrill · Netcore · Outlook 365 · Plunk · Postmark · Resend · SendGrid · SparkPost
</details>
<details> <summary><strong>SMS (37 providers)</strong></summary>
46elks · Africa's Talking · Afro SMS · Amazon SNS · Azure SMS · Bandwidth · Brevo SMS · Bulk SMS · Burst SMS · Clickatell · ClickSend · CM Telecom · Eazy SMS · Firetext · Generic SMS · Gupshup · iMedia · Infobip · iSend SMS · iSendPro SMS · Kannel · Maqsam · MessageBird · Mobishastra · Plivo · RingCentral · Sendchamp · SimpleTexting · Sinch · SMS Central · SMS77 · SMSMode · Telnyx · Termii · Twilio · Unifonic · Vonage (Nexmo)
</details>
<details> <summary><strong>Push (8 providers)</strong></summary>
APNS · App.io · Expo · FCM (Firebase Cloud Messaging) · OneSignal · Push Webhook · Pusher Beams · Pushpad
</details>
<details> <summary><strong>Chat (12 providers)</strong></summary>
Chat Webhook · Discord · GetStream · Grafana OnCall · Mattermost · Microsoft Teams · Rocket.Chat · Ryver · Slack · Telegram · WhatsApp Business · Zulip
</details>
<details> <summary><strong>In-App (1 provider)</strong></summary>
Novu Inbox (the built-in <Inbox /> channel)
</details>
10.2 Configuring a provider
- In the dashboard, go to Integrations → Add Integration.
- Pick the channel and provider (e.g. Email → Resend).
- Enter the provider credentials (API key, sender address, region, etc.).
- Set it as the active integration for that channel in the environment.
Note
Each channel can have one active provider per environment. You configure different credentials in Development vs. Production environments — see Section 13.
10.3 Provider-specific notes
- Custom SMTP uses the Nodemailer provider — point it at any SMTP server.
- Email Webhook / Chat Webhook / Push Webhook let you deliver to a service Novu doesn't have a native provider for by POSTing a normalized payload to your own endpoint.
- FCM push expects valid FCM credentials; an invalid config raises
Config is not valid for fcm. Likewise APNS raisesConfig is not valid for apns.
11. Agent Communication Infrastructure (ACI)
ACI lets an AI agent you've already built talk to users on real channels — Slack, Microsoft Teams, Telegram, WhatsApp, Email, and an in-app Inbox — through one unified conversation model. Novu handles inbound message normalization, threading, reactions, and channel-aware formatting in both directions; your agent logic stays yours.
You build the agent. Novu gives it a voice.
11.1 Connect an agent in minutes
npx novu@latest connect
This interactive flow creates an agent, lets you describe it, and connects a channel. Useful options:
| Option | Description |
|---|---|
-s, --secret-key <key> |
Use this Secret Key and skip browser auth. |
--prompt <text> |
Pre-fill the agent description. |
--anthropic-api-key <key> |
Anthropic API key for non-interactive claude runtime runs. |
--skip-slack |
Create the agent and exit without connecting a channel. |
--ci |
Force non-interactive mode (no TUI). |
Tip
ACI works with whatever stack you've built — Claude Managed Agents, the AI SDK, LangGraph, or a custom runtime. Novu connects the agent to the world; it is not the agent itself. Try the live demo at novu.co/connect.
11.2 How inbound works
When a user replies in Slack/Telegram/Email, Novu receives the platform webhook, normalizes it into the unified conversation model, and routes it to your agent's Bridge endpoint. Your agent's response is sent back out through the same channel — so you integrate once instead of maintaining a webhook handler per platform.
12. Translations & Layouts
12.1 Translations
Localize notification content per subscriber locale. Manage translation files from the CLI:
npx novu@latest translations pull --secret-key $KEY --directory ./translations
# edit files...
npx novu@latest translations push --secret-key $KEY --directory ./translations
Set each subscriber's locale (e.g. en_US, fr_FR) so Novu renders the matching translation at send time.
Note
Advanced translation management is part of Novu's enterprise edition. The community edition supports per-locale content; see the Admin Guide for the open-core boundary.
12.2 Layouts
A layout is a reusable wrapper (header/footer/branding) applied around email content so every email shares a consistent look. Create layouts in the dashboard and reference them from email steps. The no-code email editor (Maily) lets you build rich, branded emails visually.
13. Environments & Promotion
Every Novu account has at least Development and Production environments. Each environment has its own API keys, integrations (provider credentials), and subscribers.
Typical flow:
- Build and test workflows in Development (with
novu devand test triggers). - Promote / sync them to Production.
- Production uses production provider credentials and real subscribers.
Important
API keys are environment-scoped. A Development Secret Key cannot trigger Production workflows and vice-versa. Keep them in separate secrets in your deployment.
14. Common Tasks (Recipes)
14.1 Trigger to many users at once
await novu.trigger({
workflowId: 'product-update',
to: [
{ subscriberId: 'user_1', email: '[email removed]' },
{ subscriberId: 'user_2', email: '[email removed]' },
],
payload: { version: '2.0' },
});
14.2 Broadcast to a topic
Group subscribers under a topic key (e.g. team:acme) and trigger the topic to reach all of them:
await novu.trigger({
workflowId: 'team-announcement',
to: [{ type: 'Topic', topicKey: 'team:acme' }],
payload: { message: 'Maintenance tonight' },
});
14.3 Cancel a delayed/scheduled notification
If a workflow is waiting on a delay or digest, cancel it by its transactionId:
curl -X DELETE https://api.novu.co/v1/events/trigger/<transactionId> \
-H "Authorization: ApiKey $NOVU_SECRET_KEY"
14.4 Add the Inbox to an existing React app fast
npx novu-add-inbox --appId <APP_ID> --subscriberId <SUBSCRIBER_ID>
The add-inbox CLI scaffolds the Inbox into your project. Options include --region <eu|us>, --backendUrl <url>, and --socketUrl <url> for self-hosted instances.
14.5 Send a test notification from the dashboard
In the workflow editor, use Test Workflow with sample payload data (generated from your payloadSchema) to send a one-off to yourself — no code needed.
15. Troubleshooting
Format: the exact error, its cause, the fix, and how to prevent it.
Error: A bridge URL is required for the sync command, please supply it when running the command
- Cause:
novu syncwas run without--bridge-url. - Solution: Pass your deployed Bridge endpoint, e.g.
--bridge-url https://your-app.com/api/novu. - Prevention: Store the Bridge URL in CI as an environment variable and reference it in your sync step.
Error: A secret key is required for the sync command, please supply it when running the command
- Cause:
novu syncran without--secret-key. - Solution: Pass
--secret-key $NOVU_SECRET_KEY(environment-scoped key). - Prevention: Keep the Secret Key in a CI secret; never hardcode it.
Error: Config is not valid for fcm (or Config is not valid for apns)
- Cause: The push provider integration is missing or has malformed credentials.
- Solution: Re-enter the FCM service-account JSON / APNS key in Integrations, and confirm the subscriber has a registered device token for that provider.
- Prevention: Validate provider credentials right after configuring, using a test trigger to one device.
Error: notifications never appear in the <Inbox />
- Cause: Wrong
applicationIdentifier, mismatchedsubscriberId, or (self-hosted) the Inbox can't reach the websocket service. - Solution: Confirm the
applicationIdentifiermatches the environment you triggered in, thesubscriberIdmatches thetoyou triggered, and (self-hosted)backendUrl/socketUrlpoint at your API/WS services. - Prevention: Use the same env-scoped Application Identifier on both the trigger side and the Inbox.
Error: ws@3.x crashes on connection … reading subscriber.contextKeys.length (GitHub #10124)
- Cause: A version-specific bug in the websocket service when a subscriber lacks
contextKeys. - Solution: Upgrade
@novu/js/@novu/reactand the self-hostedwsimage to the latest version. - Prevention: Keep client SDK and server images on matching, current versions.
Error: SsrfBlockedError: BLOCKED_HOSTNAME / INVALID_URL / CREDENTIALS_IN_URL
- Cause: A Bridge URL, webhook URL, or provider endpoint resolves to a blocked/internal address or is malformed. Novu blocks outbound requests to private ranges to prevent SSRF.
- Solution: Use a publicly reachable HTTPS URL with no embedded credentials. For local development use the tunnel that
novu devprovides. - Prevention: Host your Bridge endpoint on a public domain; don't put
user:pass@in URLs.
Error: DataFilterValidationError / TagsFilterValidationError
- Cause: A workflow step condition (data filter or tags filter) is malformed — too many groups/values, wrong types, or both
ANDandORset. - Solution: Simplify the filter; ensure values are scalars (string/number/boolean/null), tags are strings, and you use either AND or OR (not both) at a level.
- Prevention: Build conditions in the dashboard's condition editor, which enforces the limits.
Error: trigger returns success but no email is sent
- Cause: No active Email integration in the triggered environment, the subscriber has no
email, or the channel is disabled by preferences. - Solution: Check the Activity Feed for the per-step status; configure an active Email provider; ensure the
toobject includesemail; verify the subscriber hasn't muted the channel. - Prevention: Mark must-send workflows as critical to bypass preference opt-outs.
Error: NOVU_MANAGED_CLAUDE_API_KEY is not configured (agent runs)
- Cause: A managed-agent feature needs an Anthropic key that isn't set.
- Solution: Provide
--anthropic-api-keytonovu connect, or set the key in your environment for non-interactive runs. - Prevention: Configure the agent runtime credentials before running agent flows in CI.
Error: self-hosted dashboard loads but API calls fail
- Cause:
VITE_API_HOSTNAME/VITE_WEBSOCKET_HOSTNAME(dashboard) don't match where the API/WS are reachable from the browser. - Solution: Set these to the externally reachable URLs of your API (
:3000) and WS (:3002) services. - Prevention: See the Admin Guide environment-variable reference.
Error: workflows defined in code don't show up after deploy
- Cause:
novu syncwasn't run, or it pointed at the wrong environment. - Solution: Run
novu sync --bridge-url <prod-bridge> --secret-key <prod-key>against the right environment. - Prevention: Make sync a required CI step gated on a successful deploy.
16. FAQ
Q: Do I have to use Novu Cloud? No. Novu is fully open source (MIT, with an enterprise edition under a commercial license). You can self-host the entire stack with Docker — see Section 3 and the Admin Guide.
Q: What's the difference between a workflow built in the dashboard vs. in code?
Functionally similar. The dashboard's no-code editor is great for non-engineers and quick changes; @novu/framework keeps workflows in your repo, type-safe and versioned, served from your app via the Bridge. You can mix both.
Q: What is the "Bridge"?
The Bridge is the HTTP endpoint in your app (served by @novu/framework) that Novu calls to discover and execute your code-defined workflows. novu dev tunnels to it locally; novu sync registers its URL.
Q: How do I identify users?
With your own subscriberId — use your application's user ID. Novu upserts subscribers when you trigger with a to object containing channel credentials.
Q: Can one workflow send to multiple channels? Yes. Add multiple channel steps (email, sms, inApp, push, chat) to one workflow. Conditions and preferences decide which actually fire per subscriber.
Q: How do I batch noisy notifications?
Use a digest step to aggregate events over a time window into a single message (Section 9).
Q: How do users opt out of notifications?
Through the preferences panel in the <Inbox /> (or your own UI on the preferences API). They can mute per-workflow or globally per channel. Mark truly critical workflows to bypass opt-outs.
Q: Which framework can serve @novu/framework workflows?
Next.js, Express, Remix, SvelteKit, Nuxt/H3, NestJS, and AWS Lambda are supported serve targets.
Q: How do I localize content?
Set each subscriber's locale and manage translation files with novu translations pull/push. Advanced translation management is an enterprise feature.
Q: Is the Inbox secure?
Enable HMAC subscriber verification in production so a user can only read their own feed. Without it, subscriberId is guessable.
Q: What Node version do I need? To run/self-host the platform: Node 22. The client SDKs target current LTS Node and modern browsers; React packages need React 19.
Q: Where's the full REST API reference? At docs.novu.co/api-reference. Novu generates an OpenAPI spec from the API, so the reference always matches the deployed version.
Q: How do I report a bug or request a provider? Open an issue at github.com/novuhq/novu (there are active requests for providers like WeChat, LINE, and Google Chat) or join the Discord.
17. Reference
17.1 npm packages
| Package | Role |
|---|---|
novu |
CLI (init, dev, sync, connect, wizard, translations, step). |
@novu/api |
Server SDK for triggering and managing resources. |
@novu/framework |
Code-first workflow definitions + serve adapters. |
@novu/js |
Framework-agnostic browser client for the Inbox. |
@novu/react |
React <Inbox /> and hooks. |
@novu/nextjs |
Next.js Inbox wrapper. |
@novu/react-native |
React Native notification hooks. |
@novu/stateless |
Lightweight stateless notification trigger library. |
@novu/providers |
The provider implementations (used internally). |
17.2 Key CLI commands
| Command | Most-used flags |
|---|---|
npx novu@latest init |
`-t <notifications |
npx novu@latest dev |
-p <port>, -r <route>, --no-studio, -H |
npx novu@latest sync |
--bridge-url, -s <secret-key>, -a <api-url> |
npx novu@latest connect |
--prompt, -s <secret-key>, --ci |
npx novu@latest translations pull/push |
-s, -d <directory> |
17.3 Hosted endpoints (Novu Cloud)
| Service | URL |
|---|---|
| API (US) | https://api.novu.co |
| API (EU) | https://eu.api.novu.co |
| Dashboard | https://dashboard.novu.co |
| Docs | https://docs.novu.co |
17.4 Default local/self-hosted ports
| Service | Port |
|---|---|
| API | 3000 |
| Worker (health) | 3004 |
| WebSocket (WS) | 3002 |
| Dashboard | 4000 |
| Local Studio (dev) | 2022 |
17.5 Useful environment variables (client/CLI)
| Variable | Purpose |
|---|---|
NOVU_SECRET_KEY |
Server-side API key for triggering. |
NOVU_API_URL / NOVU_API_KEY |
Base URL and key used by @novu/framework. |
NEXT_PUBLIC_NOVU_APP_ID |
Public Application Identifier for the Inbox. |
NEXT_PUBLIC_NOVU_SOCKET_URL |
WebSocket URL for the Inbox (self-hosted). |
NOVU_CLI_DEBUG |
Verbose CLI logging when true. |
ANALYTICS_ENABLED |
Toggle CLI analytics. |
Note
For the full server-side environment-variable reference, deployment topology, scaling, and security hardening, see the Novu Admin Guide.