# Novu User Guide

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 the `novu` CLI). This guide documents the open-source platform at commit `b6ab172`. The always-current API reference and channel-specific guides live at [docs.novu.co](https://docs.novu.co).

---

## Table of Contents

1. [Quick Start](#1-quick-start)
2. [Core Concepts](#2-core-concepts)
3. [Installation & Setup](#3-installation--setup)
4. [Sending Your First Notification](#4-sending-your-first-notification)
5. [Building Workflows in Code (`@novu/framework`)](#5-building-workflows-in-code-novuframework)
6. [The `novu` CLI Reference](#6-the-novu-cli-reference)
7. [Embedding the Inbox](#7-embedding-the-inbox)
8. [Subscribers & Preferences](#8-subscribers--preferences)
9. [Digest, Delay & Scheduling](#9-digest-delay--scheduling)
10. [Channels & Providers](#10-channels--providers)
11. [Agent Communication Infrastructure (ACI)](#11-agent-communication-infrastructure-aci)
12. [Translations & Layouts](#12-translations--layouts)
13. [Environments & Promotion](#13-environments--promotion)
14. [Common Tasks (Recipes)](#14-common-tasks-recipes)
15. [Troubleshooting](#15-troubleshooting)
16. [FAQ](#16-faq)
17. [Reference](#17-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](https://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](#3-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:

```bash
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:

```bash
npx novu@latest dev --port 4000
```

```text
✔ Novu Dev Studio running
  Local Bridge tunnel established
  Studio: http://localhost:2022
```

> [!TIP]
> The `npx novu@latest dev` command 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-studio` to skip the UI, or `--headless` to avoid opening a browser.

### 1.2 The three things every Novu app does

Every Novu integration boils down to three steps:

1. **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`).
2. **Trigger the workflow** — make one API call (`POST /v1/events/trigger`) from your backend with a `workflowId`, a `to` subscriber, and a `payload`.
3. **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.

```text
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 `subscriberId` is 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](#11-agent-communication-infrastructure-aci)).

---

## 3. Installation & Setup

### 3.1 Choosing how to run Novu

=== "Novu Cloud (hosted)"

    1. Create a free account at [dashboard.novu.co](https://dashboard.novu.co).
    2. Copy your **Secret Key** (a.k.a. API key) and **Application Identifier** from **Settings → API Keys**.
    3. Use `https://api.novu.co` as your API base URL (or `https://eu.api.novu.co` for the EU region).

=== "Self-hosted (Docker)"

    Run the one-line community installer on a host with Docker, `curl`, and `openssl`:

    ```bash
    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:

    ```text
    ▸ 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](#5-building-workflows-in-code-novuframework)), trigger it from your backend.

### 4.1 Trigger with the server SDK

```ts
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',
  },
});
```

```text
{ acknowledged: true, status: 'processed', transactionId: '...' }
```

- `workflowId` — the workflow's trigger identifier.
- `to` — the recipient. Pass a `subscriberId` (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:

```bash
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

```ts
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 `workflowId` you 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 steps `step.digest`, `step.delay`, and `step.custom`. The exact options for each step are documented at [docs.novu.co/workflows](https://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`):

```ts
// 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:

```bash
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**:

```bash
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 sync` to 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](#11-agent-communication-infrastructure-aci). |
| `novu wizard` | Interactive setup wizard (full | inbox | workflows goals). |
| `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`

```text
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`

```text
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 sync` fails fast if `--bridge-url` or `--secret-key` is missing, printing `A bridge URL is required for the sync command…` or `A 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`

```bash
# 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=true` to see verbose CLI request logging. CLI analytics can be disabled with `ANALYTICS_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

```tsx
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

```tsx
'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:

```tsx
<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](https://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 `subscriberId` is 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:

```ts
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.

```ts
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.

```ts
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

1. In the dashboard, go to **Integrations → Add Integration**.
2. Pick the channel and provider (e.g. Email → Resend).
3. Enter the provider credentials (API key, sender address, region, etc.).
4. 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](#13-environments--promotion).

### 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 raises `Config 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

```bash
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](https://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:

```bash
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:

1. Build and test workflows in **Development** (with `novu dev` and test triggers).
2. **Promote / sync** them to **Production**.
3. 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

```ts
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:

```ts
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`:

```bash
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

```bash
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 sync` was 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 sync` ran 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`, mismatched `subscriberId`, or (self-hosted) the Inbox can't reach the websocket service.
- **Solution:** Confirm the `applicationIdentifier` matches the environment you triggered in, the `subscriberId` matches the `to` you triggered, and (self-hosted) `backendUrl`/`socketUrl` point 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/react` and the self-hosted `ws` image 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 dev` provides.
- **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 `AND` and `OR` set.
- **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 `to` object includes `email`; 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-key` to `novu 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 sync` wasn'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](#3-installation--setup) 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](#9-digest-delay--scheduling)).

**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](https://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](https://github.com/novuhq/novu) (there are active requests for providers like WeChat, LINE, and Google Chat) or join the [Discord](https://discord.novu.co).

---

## 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|agent>` |
| `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**.

---

_Generated June 2026 from novuhq/novu at b6ab172. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
