novu-inbox-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inbox Integration

收件箱集成

Add an in-app notification center to your web application. The Inbox component provides a bell icon, notification feed, read/archive management, action buttons, and real-time WebSocket updates — all theme-able and personalizable to match your product.
为你的Web应用添加应用内通知中心。Inbox组件提供铃铛图标、通知流、已读/归档管理、操作按钮以及实时WebSocket更新功能——所有内容均可自定义主题和个性化设置,以匹配你的产品风格。

Packages

包列表

PackageUse For
@novu/react
React 18/19 applications
@novu/nextjs
Next.js (App Router + Pages Router)
@novu/js
Vanilla JavaScript / non-React frameworks
包名适用场景
@novu/react
React 18/19 应用
@novu/nextjs
Next.js(App Router + Pages Router)
@novu/js
原生JavaScript / 非React框架

React Quick Start

React快速开始

bash
npm install @novu/react
tsx
import { Inbox } from "@novu/react";

function App() {
  return (
    <Inbox
      applicationIdentifier="YOUR_NOVU_APP_ID"
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"  // Required if HMAC encryption is enabled
    />
  );
}
This renders a bell icon with unread count. Clicking it opens a popover with the notification feed.
bash
npm install @novu/react
tsx
import { Inbox } from "@novu/react";

function App() {
  return (
    <Inbox
      applicationIdentifier="YOUR_NOVU_APP_ID"
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"  // 启用HMAC加密时必填
    />
  );
}
这会渲染一个带有未读计数的铃铛图标。点击图标会弹出包含通知流的弹窗。

Next.js

Next.js集成

bash
npm install @novu/nextjs
bash
npm install @novu/nextjs

App Router

App Router

tsx
// components/NotificationInbox.tsx
"use client";

import { Inbox } from "@novu/nextjs";

export function NotificationInbox() {
  return (
    <Inbox
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    />
  );
}
Important: The Inbox is a client component — use
"use client"
directive in Next.js App Router.
tsx
// components/NotificationInbox.tsx
"use client";

import { Inbox } from "@novu/nextjs";

export function NotificationInbox() {
  return (
    <Inbox
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    />
  );
}
重要提示: Inbox是客户端组件——在Next.js App Router中需使用
"use client"
指令。

Pages Router

Pages Router

tsx
import { Inbox } from "@novu/nextjs";

export default function NotificationsPage() {
  return (
    <Inbox
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    />
  );
}
tsx
import { Inbox } from "@novu/nextjs";

export default function NotificationsPage() {
  return (
    <Inbox
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    />
  );
}

Composable Components

可组合组件

The
<Inbox>
component is composable. When you pass children, it acts as a context provider and you compose the UI from primitives:
ComponentPurpose
<Bell />
Bell icon with unread count
<Notifications />
Notification feed (header + list + footer)
<InboxContent />
Same as
<Notifications />
plus the Preferences page
<Preferences />
Standalone preferences panel
tsx
import { Inbox, Bell, Notifications, Preferences } from "@novu/react";

function App() {
  return (
    <Inbox
      applicationIdentifier="YOUR_NOVU_APP_ID"
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    >
      <Bell />
      <Notifications />
      <Preferences />
    </Inbox>
  );
}
Use these primitives to build a custom popover, modal, drawer, or full-page notification experience.
<Inbox>
组件支持组合使用。当传入子组件时,它会作为上下文提供者,你可以使用基础组件构建自定义UI:
组件用途
<Bell />
带有未读计数的铃铛图标
<Notifications />
通知流(头部+列表+底部)
<InboxContent />
包含偏好设置页面的完整通知流
<Preferences />
独立的偏好设置面板
tsx
import { Inbox, Bell, Notifications, Preferences } from "@novu/react";

function App() {
  return (
    <Inbox
      applicationIdentifier="YOUR_NOVU_APP_ID"
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    >
      <Bell />
      <Notifications />
      <Preferences />
    </Inbox>
  );
}
使用这些基础组件可以构建自定义弹窗、模态框、侧边栏或全页式通知体验。

Branding the Inbox

收件箱品牌定制

The Inbox is fully themeable via the
appearance
prop. It supports four keys:
KeyPurpose
baseTheme
Apply a predefined theme (e.g.
dark
)
variables
Global design tokens (colors, fonts, radius, severity colors)
elements
Per-element styles (style object, class string, or context callback)
icons
Replace built-in icons with your own React components
Styles are auto-injected into
<head>
(or the shadow root if rendered inside a shadow DOM). When both
baseTheme
and
variables
are provided,
variables
win.
Inspiration: the Inbox Playground showcases pre-styled variants like Notion and Reddit.
通过
appearance
属性可对收件箱进行完全主题定制,支持以下四个配置项:
配置项用途
baseTheme
应用预定义主题(如
dark
variables
全局设计令牌(颜色、字体、圆角、优先级颜色)
elements
元素级样式(样式对象、类名或上下文回调函数)
icons
使用自定义React组件替换内置图标
样式会自动注入到
<head>
中(若在Shadow DOM内渲染则注入到Shadow根节点)。当同时提供
baseTheme
variables
时,
variables
的配置优先级更高。
灵感来源:收件箱演示 playground展示了Notion和Reddit等预定义样式变体。

Dark mode (and other base themes)

深色模式(及其他基础主题)

tsx
import { Inbox } from "@novu/react";
import { dark } from "@novu/react/themes";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{ baseTheme: dark }}
/>
tsx
import { Inbox } from "@novu/react";
import { dark } from "@novu/react/themes";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{ baseTheme: dark }}
/>

Global variables

全局变量配置

tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    variables: {
      colorPrimary: "#0081F1",
      colorBackground: "#ffffff",
      colorForeground: "#1A1523",
      colorPrimaryForeground: "#ffffff",
      colorSecondary: "#F1F0EF",
      colorCounter: "#E5484D",
      colorCounterForeground: "#ffffff",
      colorNeutral: "#E0DEDC",
      colorShadow: "rgba(0,0,0,0.08)",
      fontSize: "14px",
      borderRadius: "8px",
      colorSeverityHigh: "#E5484D",
      colorSeverityMedium: "#F76808",
      colorSeverityLow: "#3E63DD",
    },
  }}
/>
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    variables: {
      colorPrimary: "#0081F1",
      colorBackground: "#ffffff",
      colorForeground: "#1A1523",
      colorPrimaryForeground: "#ffffff",
      colorSecondary: "#F1F0EF",
      colorCounter: "#E5484D",
      colorCounterForeground: "#ffffff",
      colorNeutral: "#E0DEDC",
      colorShadow: "rgba(0,0,0,0.08)",
      fontSize: "14px",
      borderRadius: "8px",
      colorSeverityHigh: "#E5484D",
      colorSeverityMedium: "#F76808",
      colorSeverityLow: "#3E63DD",
    },
  }}
/>

Element-level styling (Tailwind, CSS Modules, inline styles)

元素级样式(Tailwind、CSS Modules、内联样式)

Each element accepts a string of class names, a style object, or a function
(context) => string
for runtime conditionals.
tsx
import inboxStyles from "./inbox.module.css";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    elements: {
      bellIcon: ({ unreadCount }) =>
        unreadCount.total > 10
          ? "p-4 bg-white rounded-full [--bell-gradient-end:var(--color-red-500)]"
          : "p-4 bg-white rounded-full",
      notification: ({ notification }) =>
        notification.data?.priority === "high"
          ? "bg-red-50 ring-1 ring-red-300 rounded-lg"
          : "bg-white rounded-lg shadow-sm hover:bg-gray-50",
      notificationSubject: { fontWeight: 600 },
      notificationBody: inboxStyles.body,
    },
  }}
/>
To find an element key, inspect the DOM: any class starting with
nv-
(visible just before a 🔔 emoji in DevTools) maps to a key in
appearance.elements
(drop the
nv-
prefix). TS autocomplete lists all available keys.
每个元素支持传入类名字符串、样式对象或运行时条件判断函数
(context) => string
tsx
import inboxStyles from "./inbox.module.css";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    elements: {
      bellIcon: ({ unreadCount }) =>
        unreadCount.total > 10
          ? "p-4 bg-white rounded-full [--bell-gradient-end:var(--color-red-500)]"
          : "p-4 bg-white rounded-full",
      notification: ({ notification }) =>
        notification.data?.priority === "high"
          ? "bg-red-50 ring-1 ring-red-300 rounded-lg"
          : "bg-white rounded-lg shadow-sm hover:bg-gray-50",
      notificationSubject: { fontWeight: 600 },
      notificationBody: inboxStyles.body,
    },
  }}
/>
如需查找元素配置键,可检查DOM:所有以
nv-
开头的类名(在开发者工具中🔔 emoji前可见)对应
appearance.elements
中的键(去掉
nv-
前缀)。TypeScript自动补全会列出所有可用键。

Custom icons

自定义图标

Replace any built-in icon by returning a React component from
appearance.icons
:
tsx
import { RiSettings3Fill, RiNotification3Fill } from "react-icons/ri";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    icons: {
      bell: () => <RiNotification3Fill />,
      cogs: () => <RiSettings3Fill />,
    },
  }}
/>
Common icon keys:
bell
,
cogs
,
dots
,
arrowDown
,
arrowDropDown
,
arrowLeft
,
arrowRight
,
check
,
clock
,
trash
,
markAsRead
,
markAsUnread
,
markAsArchived
,
markAsUnarchived
,
email
,
sms
,
push
,
inApp
,
chat
. To find more, inspect classes that start with
nv-
and contain a 🖼️ emoji.
通过
appearance.icons
返回React组件替换任意内置图标:
tsx
import { RiSettings3Fill, RiNotification3Fill } from "react-icons/ri";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{
    icons: {
      bell: () => <RiNotification3Fill />,
      cogs: () => <RiSettings3Fill />,
    },
  }}
/>
常见图标键:
bell
cogs
dots
arrowDown
arrowDropDown
arrowLeft
arrowRight
check
clock
trash
markAsRead
markAsUnread
markAsArchived
markAsUnarchived
email
sms
push
inApp
chat
。如需查找更多,可检查包含🖼️ emoji的
nv-
开头类名。

Severity styling

优先级样式

Notifications and the bell are styled by severity (
high
,
medium
,
low
). Override colors via
variables
:
Severity is a visual dial only. The workflow-level
critical: true
flag is independent — it changes runtime delivery (bypass preferences, skip digest), not Inbox styling.
critical
workflows that should also stand out visually should set
severity: 'high'
explicitly. See
design-workflow/references/severity-and-critical.md
for the full design rules.
tsx
appearance: {
  variables: {
    colorSeverityHigh: "#E5484D",
    colorSeverityMedium: "#F76808",
    colorSeverityLow: "#3E63DD",
  },
}
…or per element:
tsx
appearance: {
  elements: {
    severityHigh__notificationBar: { backgroundColor: "red" },
    severityHigh__bellContainer: "ring-2 ring-red-500",
    severityGlowHigh__bellSeverityGlow: "bg-red-500",
  },
}
By default the bell takes the color of the highest-severity unread notification.
通知和铃铛会根据优先级(
high
medium
low
)设置样式。可通过
variables
覆盖颜色:
优先级仅为视觉标识。工作流层面的
critical: true
标记是独立的——它会改变运行时推送逻辑(绕过偏好设置、跳过摘要),但不会影响收件箱样式。若需关键工作流在视觉上突出显示,需显式设置
severity: 'high'
。完整设计规则请参考
design-workflow/references/severity-and-critical.md
tsx
appearance: {
  variables: {
    colorSeverityHigh: "#E5484D",
    colorSeverityMedium: "#F76808",
    colorSeverityLow: "#3E63DD",
  },
}
…或按元素单独配置:
tsx
appearance: {
  elements: {
    severityHigh__notificationBar: { backgroundColor: "red" },
    severityHigh__bellContainer: "ring-2 ring-red-500",
    severityGlowHigh__bellSeverityGlow: "bg-red-500",
  },
}
默认情况下,铃铛会使用未读通知中最高优先级的颜色。

Responsive Inbox

响应式收件箱

tsx
<Inbox
  /* ... */
  appearance={{ elements: { popoverContent: "novu-popover-content" } }}
/>
css
.novu-popover-content { max-width: 500px; }
@media (max-width: 768px) { .novu-popover-content { max-width: 350px; } }
@media (max-width: 480px) { .novu-popover-content { max-width: 250px; } }
See Branding & Styling Reference for the full variable list, severity element keys, dynamic callback signatures, and Notion/Reddit-style presets.
tsx
<Inbox
  /* ... */
  appearance={{ elements: { popoverContent: "novu-popover-content" } }}
/>
css
.novu-popover-content { max-width: 500px; }
@media (max-width: 768px) { .novu-popover-content { max-width: 350px; } }
@media (max-width: 480px) { .novu-popover-content { max-width: 250px; } }
完整变量列表、优先级元素键、动态回调签名以及Notion/Reddit风格预设,请参考品牌与样式参考文档

Personalization

个性化定制

Render props

渲染属性

Override individual parts of a notification — keep the surrounding chrome (action buttons, hover state, etc.) intact:
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  renderBell={(unreadCount) => <MyBell count={unreadCount.total} />}
  renderAvatar={(notification) => <Avatar src={notification.avatar} />}
  renderSubject={(notification) => <strong>{notification.subject}</strong>}
  renderBody={(notification) => <p>{notification.body}</p>}
  renderDefaultActions={(notification) => <MyActions notification={notification} />}
  renderCustomActions={(notification) => (
    <PrimarySecondaryButtons notification={notification} />
  )}
/>
Use
renderNotification
only when you need full control of the item — you'll need to re-implement default actions (mark as read, archive, snooze) yourself.
tsx
<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div className="custom-row">
      <h3>{notification.subject}</h3>
      <p>{notification.body}</p>
    </div>
  )}
/>
覆盖通知的任意部分——保留周围的操作按钮、悬停状态等框架:
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  renderBell={(unreadCount) => <MyBell count={unreadCount.total} />}
  renderAvatar={(notification) => <Avatar src={notification.avatar} />}
  renderSubject={(notification) => <strong>{notification.subject}</strong>}
  renderBody={(notification) => <p>{notification.body}</p>}
  renderDefaultActions={(notification) => <MyActions notification={notification} />}
  renderCustomActions={(notification) => (
    <PrimarySecondaryButtons notification={notification} />
  )}
/>
仅当需要完全控制通知项时使用
renderNotification
——此时需自行实现默认操作(标记为已读、归档、稍后提醒)。
tsx
<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div className="custom-row">
      <h3>{notification.subject}</h3>
      <p>{notification.body}</p>
    </div>
  )}
/>

Conditional display

条件显示

renderNotification
receives the full notification — branch on
tags
,
data
,
severity
, or
workflow.identifier
:
tsx
renderNotification={(notification) => {
  if (notification.severity === SeverityLevelEnum.HIGH) return <HighPriorityRow notification={notification} />;
  if (notification.tags?.includes("billing")) return <BillingRow notification={notification} />;
  if (notification.data?.priority === "high") return <UrgentRow notification={notification} />;

  return <DefaultRow notification={notification} />;
}}
renderNotification
会接收完整的通知对象——可根据
tags
data
severity
workflow.identifier
进行分支渲染:
tsx
renderNotification={(notification) => {
  if (notification.severity === SeverityLevelEnum.HIGH) return <HighPriorityRow notification={notification} />;
  if (notification.tags?.includes("billing")) return <BillingRow notification={notification} />;
  if (notification.data?.priority === "high") return <UrgentRow notification={notification} />;

  return <DefaultRow notification={notification} />;
}}

HTML in notification content

通知内容中的HTML

To render rich HTML in
subject
/
body
:
  1. Disable Disable content sanitization in the In-App step in your workflow.
  2. Render with
    dangerouslySetInnerHTML
    in a render prop:
tsx
<Inbox
  /* ... */
  renderBody={(notification) => (
    <div dangerouslySetInnerHTML={{ __html: notification.body }} />
  )}
  renderSubject={(notification) => (
    <span dangerouslySetInnerHTML={{ __html: notification.subject }} />
  )}
/>
Only enable this if you fully control the trigger payload — raw HTML opens an XSS surface area.
如需在
subject
/
body
中渲染富HTML:
  1. 在工作流的应用内步骤中禁用禁用内容 sanitization
  2. 在渲染属性中使用
    dangerouslySetInnerHTML
tsx
<Inbox
  /* ... */
  renderBody={(notification) => (
    <div dangerouslySetInnerHTML={{ __html: notification.body }} />
  )}
  renderSubject={(notification) => (
    <span dangerouslySetInnerHTML={{ __html: notification.subject }} />
  )}
/>
仅当你完全控制触发负载时才启用此功能——原始HTML会带来XSS攻击风险。

Notification click behavior

通知点击行为

Hook the Inbox into your router. Novu calls
routerPush
with the
redirect.url
defined in your workflow:
tsx
import { useRouter } from "next/navigation";

const router = useRouter();

<Inbox
  /* ... */
  routerPush={(path) => router.push(path)}
  onNotificationClick={(notification) => track("inbox_notification_click", { id: notification.id })}
  onPrimaryActionClick={(notification) => doSomething(notification.primaryAction)}
  onSecondaryActionClick={(notification) => doSomethingElse(notification.secondaryAction)}
/>
Works with React Router (
useNavigate()
), Remix (
useNavigate()
), Gatsby (
navigate()
), and any custom router.
See Personalization Reference for full render-prop signatures,
renderCustomActions
styling examples, popover composition with Radix / shadcn Drawer, and conditional UI patterns.
将收件箱与你的路由集成。Novu会调用
routerPush
并传入工作流中定义的
redirect.url
tsx
import { useRouter } from "next/navigation";

const router = useRouter();

<Inbox
  /* ... */
  routerPush={(path) => router.push(path)}
  onNotificationClick={(notification) => track("inbox_notification_click", { id: notification.id })}
  onPrimaryActionClick={(notification) => doSomething(notification.primaryAction)}
  onSecondaryActionClick={(notification) => doSomethingElse(notification.secondaryAction)}
/>
此功能兼容React Router(
useNavigate()
)、Remix(
useNavigate()
)、Gatsby(
navigate()
)以及任何自定义路由。
完整渲染属性签名、
renderCustomActions
样式示例、与Radix/shadcn Drawer的弹窗组合以及条件UI模式,请参考个性化定制参考文档

Tabs

标签页

Group notifications into tabs by tags, severity, or
data
properties
:
tsx
import { Inbox, SeverityLevelEnum } from "@novu/react";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  tabs={[
    { label: "All", filter: { tags: [] } },
    { label: "Promotions", filter: { tags: ["promotions"] } },
    { label: "Security", filter: { tags: ["security", "alert"] } },
    { label: "Critical", filter: { severity: SeverityLevelEnum.HIGH } },
    { label: "High Priority", filter: { data: { priority: "high" } } },
    {
      label: "Billing",
      filter: { tags: ["billing"], data: { entity: "invoice" } },
    },
  ]}
/>
  • Tags are workflow-level — assign them in the workflow editor. Multiple tags use
    OR
    logic.
  • Severity comes from the In-App step's severity setting (
    HIGH
    ,
    MEDIUM
    ,
    LOW
    ).
  • data
    comes from the data object defined per In-App step.
Use the
useCounts
hook
to render unread counts per tab.
通过标签优先级或**
data
属性**将通知分组到标签页:
tsx
import { Inbox, SeverityLevelEnum } from "@novu/react";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  tabs={[
    { label: "全部", filter: { tags: [] } },
    { label: "推广", filter: { tags: ["promotions"] } },
    { label: "安全", filter: { tags: ["security", "alert"] } },
    { label: "紧急", filter: { severity: SeverityLevelEnum.HIGH } },
    { label: "高优先级", filter: { data: { priority: "high" } } },
    {
      label: "账单",
      filter: { tags: ["billing"], data: { entity: "invoice" } },
    },
  ]}
/>
  • 标签是工作流层面的——在工作流编辑器中分配。多个标签使用
    OR
    逻辑。
  • 优先级来自应用内步骤的优先级设置(
    HIGH
    MEDIUM
    LOW
    )。
  • **
    data
    **来自每个应用内步骤中定义的data对象
使用
useCounts
hook
渲染每个标签页的未读计数。

Multi-Tenancy with Contexts

基于上下文的多租户

Use Contexts to scope the Inbox to a tenant, workspace, or feature area. The Inbox shows only notifications whose trigger context matches the Inbox context exactly.
使用上下文将收件箱范围限定到租户、工作区或功能区域。收件箱仅显示触发上下文与收件箱上下文完全匹配的通知。

1. Trigger workflows with context

1. 使用上下文触发工作流

typescript
await novu.trigger({
  workflowId: "invoice-paid",
  to: { subscriberId: "user-123" },
  payload: { amount: "$250" },
  context: {
    tenant: {
      id: "acme-corp",
      data: { name: "Acme Corporation", plan: "enterprise" },
    },
  },
});
typescript
await novu.trigger({
  workflowId: "invoice-paid",
  to: { subscriberId: "user-123" },
  payload: { amount: "$250" },
  context: {
    tenant: {
      id: "acme-corp",
      data: { name: "Acme Corporation", plan: "enterprise" },
    },
  },
});

2. Pass the matching context to the Inbox

2. 将匹配的上下文传入收件箱

tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="user-123"
  subscriberHash="HMAC_HASH"
  context={{
    tenant: {
      id: "acme-corp",
      data: { name: "Acme Corporation", plan: "enterprise" },
    },
  }}
/>
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="user-123"
  subscriberHash="HMAC_HASH"
  context={{
    tenant: {
      id: "acme-corp",
      data: { name: "Acme Corporation", plan: "enterprise" },
    },
  }}
/>

3. Secure the context with
contextHash

3. 使用
contextHash
保护上下文

Because
context
is set client-side, a hostile user could swap tenant IDs. Generate an HMAC hash of the canonicalized context server-side:
typescript
import { createHmac } from "crypto";
import { canonicalize } from "@tufjs/canonical-json";

const context = {
  tenant: { id: "acme-corp", data: { name: "Acme Corporation", plan: "enterprise" } },
};

const contextHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
  .update(canonicalize(context))
  .digest("hex");
Pass it alongside the
context
:
tsx
<Inbox
  /* ... */
  context={context}
  contextHash={contextHash}
/>
由于
context
在客户端设置,恶意用户可能会替换租户ID。在服务端生成规范化上下文的HMAC哈希:
typescript
import { createHmac } from "crypto";
import { canonicalize } from "@tufjs/canonical-json";

const context = {
  tenant: { id: "acme-corp", data: { name: "Acme Corporation", plan: "enterprise" } },
};

const contextHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
  .update(canonicalize(context))
  .digest("hex");
将其与
context
一起传入:
tsx
<Inbox
  /* ... */
  context={context}
  contextHash={contextHash}
/>

Context match rules

上下文匹配规则

Workflow ContextInbox ContextDisplayed?
{ tenant: "acme" }
{ tenant: "acme" }
{}
{}
{ tenant: "acme" }
{}
{}
{ tenant: "acme" }
{ tenant: "acme" }
{ tenant: "globex" }
Context that doesn't yet exist in Novu is auto-created. Existing context data is not auto-updated to prevent overwrites.
See Multi-Tenancy Reference for full setup, dashboard management, and dynamic content rendering with
{{context}}
.
工作流上下文收件箱上下文是否显示?
{ tenant: "acme" }
{ tenant: "acme" }
{}
{}
{ tenant: "acme" }
{}
{}
{ tenant: "acme" }
{ tenant: "acme" }
{ tenant: "globex" }
Novu中不存在的上下文会自动创建。现有上下文数据不会自动更新,以防止被覆盖。
完整设置、仪表盘管理以及使用
{{context}}
的动态内容渲染,请参考多租户参考文档

Data Object

数据对象

Each In-App step supports a custom data object — up to 10 scalar key-value pairs (string, number, boolean, null; strings ≤ 256 chars) defined in the workflow editor. Values can be static (
"status": "merged"
) or dynamic (
"firstName": "{{subscriber.firstName}}"
).
Access it client-side as
notification.data
and use it for render decisions, conditional styling, and tab filtering.
tsx
<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div>
      <span>{notification.data?.emoji}</span>
      <strong>{notification.data?.firstName}</strong>
      <p>{notification.body}</p>
    </div>
  )}
/>
Type the data object globally for autocomplete:
ts
declare global {
  interface NotificationData {
    reactionType?: string;
    entityId?: string;
    userName?: string;
  }
}
Don't store secrets in
data
— it's returned to the client. Never spread the entire trigger payload into
data
.
每个应用内步骤支持自定义数据对象——最多10个标量键值对(字符串、数字、布尔值、null;字符串长度≤256字符),在工作流编辑器中定义。值可以是静态的(
"status": "merged"
)或动态的(
"firstName": "{{subscriber.firstName}}"
)。
在客户端可通过
notification.data
访问该对象,并用于渲染决策、条件样式和标签页过滤。
tsx
<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div>
      <span>{notification.data?.emoji}</span>
      <strong>{notification.data?.firstName}</strong>
      <p>{notification.body}</p>
    </div>
  )}
/>
全局类型定义数据对象以获得自动补全:
ts
declare global {
  interface NotificationData {
    reactionType?: string;
    entityId?: string;
    userName?: string;
  }
}
不要在
data
中存储敏感信息——它会返回给客户端。切勿将整个触发负载直接传入
data

Custom Popover

自定义弹窗

Mount the notification feed inside any popover, drawer, or page layout. Use
<Bell />
(or your own trigger) plus
<Notifications />
or
<InboxContent />
:
tsx
import { Inbox, InboxContent, Bell } from "@novu/react";
import { Popover, PopoverTrigger, PopoverContent } from "@radix-ui/react-popover";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
>
  <Popover>
    <PopoverTrigger>
      <Bell />
    </PopoverTrigger>
    <PopoverContent className="h-[600px] w-[400px] p-0">
      <InboxContent />
    </PopoverContent>
  </Popover>
</Inbox>
The same pattern works with shadcn
<Drawer>
, Headless UI, or a route-level page (mount
<InboxContent />
directly without any popover). All customization props (
appearance
,
localization
,
tabs
,
routerPush
, render props) flow through the
<Inbox>
provider.
将通知流挂载到任意弹窗、侧边栏或页面布局中。使用
<Bell />
(或自定义触发器)搭配
<Notifications />
<InboxContent />
tsx
import { Inbox, InboxContent, Bell } from "@novu/react";
import { Popover, PopoverTrigger, PopoverContent } from "@radix-ui/react-popover";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
>
  <Popover>
    <PopoverTrigger>
      <Bell />
    </PopoverTrigger>
    <PopoverContent className="h-[600px] w-[400px] p-0">
      <InboxContent />
    </PopoverContent>
  </Popover>
</Inbox>
相同模式适用于shadcn
<Drawer>
、Headless UI或路由级页面(直接挂载
<InboxContent />
无需弹窗)。所有自定义属性(
appearance
localization
tabs
routerPush
、渲染属性)都会通过
<Inbox>
提供者传递。

Localization

本地化

Override Inbox UI text — useful for multi-language apps or matching your product voice:
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  localization={{
    locale: "en-US",
    "inbox.filters.labels.default": "Notifications",
    "inbox.filters.dropdownOptions.unread": "Unread only",
    "notifications.emptyNotice": "You're all caught up.",
    "notifications.actions.readAll": "Mark all as read",
    "notification.actions.archive.tooltip": "Move to archive",
    "preferences.title": "Notification Preferences",
    dynamic: {
      "new-comment-on-post": "Post comments",
      "new-follower-digest": "New Follower Updates",
    },
  }}
/>
  • Localization changes UI text only. To translate notification content, use Workflow Translations.
  • Use the
    dynamic
    map to localize workflow names shown in the Preferences UI.
  • The full key list lives in
    defaultLocalization.ts
    .
覆盖收件箱UI文本——适用于多语言应用或匹配产品风格:
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  localization={{
    locale: "en-US",
    "inbox.filters.labels.default": "通知",
    "inbox.filters.dropdownOptions.unread": "仅显示未读",
    "notifications.emptyNotice": "你已查看所有通知。",
    "notifications.actions.readAll": "标记全部为已读",
    "notification.actions.archive.tooltip": "移至归档",
    "preferences.title": "通知偏好设置",
    dynamic: {
      "new-comment-on-post": "帖子评论",
      "new-follower-digest": "新关注者更新",
    },
  }}
/>
  • 本地化仅修改UI文本。如需翻译通知内容,请使用工作流翻译
  • 使用
    dynamic
    映射本地化偏好设置UI中显示的工作流名称。
  • 完整键列表请参考
    defaultLocalization.ts

HMAC Authentication

HMAC认证

Required in production to prevent subscriber impersonation. See https://docs.novu.co/platform/inbox/prepare-for-production for the full guide.
生产环境必填,以防止订阅者冒充。完整指南请参考https://docs.novu.co/platform/inbox/prepare-for-production。

Generate the hash (server-side)

生成哈希(服务端)

typescript
import { createHmac } from "crypto";

const subscriberHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
  .update(subscriberId)
  .digest("hex");
typescript
import { createHmac } from "crypto";

const subscriberHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
  .update(subscriberId)
  .digest("hex");

Python

Python示例

python
import hmac, hashlib

subscriber_hash = hmac.new(
    NOVU_SECRET_KEY.encode(),
    subscriber_id.encode(),
    hashlib.sha256,
).hexdigest()
python
import hmac, hashlib

subscriber_hash = hmac.new(
    NOVU_SECRET_KEY.encode(),
    subscriber_id.encode(),
    hashlib.sha256,
).hexdigest()

Pass to the component

传入组件

tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  subscriberHash={subscriberHash}
/>
If you also pass a
context
, generate a
contextHash
(see Multi-Tenancy).
tsx
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  subscriberHash={subscriberHash}
/>
若同时传入
context
,需生成
contextHash
(参考多租户)。

Common Pitfalls

常见陷阱

  1. applicationIdentifier
    is NOT the same as
    NOVU_SECRET_KEY
    — the app ID is a public identifier safe for client-side use. The secret key is server-only.
  2. HMAC hash is mandatory in production — without it, anyone can impersonate a subscriber by guessing their ID.
  3. The Inbox only shows notifications from workflows with an
    inApp
    step
    — if your workflow doesn't include
    step.inApp()
    , nothing appears.
  4. "use client"
    is required in Next.js App Router
    — the Inbox component is client-side only.
  5. Real-time updates are automatic — the Inbox uses WebSockets internally. No additional setup needed.
  6. @novu/react
    vs
    @novu/nextjs
    — use
    @novu/nextjs
    for Next.js apps (handles SSR edge cases),
    @novu/react
    for all other React apps.
  7. variables
    override
    baseTheme
    — when both are set in
    appearance
    , variables win. Set variables in dark/light themes intentionally.
  8. Element callbacks return strings
    (context) => string
    returns class names, not style objects. For style objects use a static value.
  9. Context filtering is exact-match — passing
    context={{}}
    to the Inbox hides any notification triggered with a non-empty context, and vice-versa.
  10. Don't store secrets in
    notification.data
    — it's sent to the client.
  11. renderNotification
    removes default actions
    — use granular render props (
    renderSubject
    ,
    renderBody
    ,
    renderAvatar
    ,
    renderDefaultActions
    ,
    renderCustomActions
    ) when you want to keep mark-as-read / archive / snooze affordances.
  12. HTML rendering requires both steps — disabling sanitization in the workflow and using
    dangerouslySetInnerHTML
    in a render prop. Either alone has no effect.
  1. applicationIdentifier
    NOVU_SECRET_KEY
    不同
    ——应用ID是公开标识符,可安全用于客户端。密钥仅用于服务端。
  2. 生产环境必须使用HMAC哈希——否则任何人都可以通过猜测订阅者ID冒充他人。
  3. 收件箱仅显示包含
    inApp
    步骤的工作流通知
    ——若工作流未包含
    step.inApp()
    ,则不会显示任何内容。
  4. Next.js App Router中必须使用
    "use client"
    ——Inbox组件仅支持客户端。
  5. 实时更新自动生效——收件箱内部使用WebSocket,无需额外设置。
  6. @novu/react
    vs
    @novu/nextjs
    ——Next.js应用使用
    @novu/nextjs
    (处理SSR边缘情况),其他React应用使用
    @novu/react
  7. variables
    覆盖
    baseTheme
    ——当
    appearance
    中同时设置两者时,
    variables
    优先级更高。需在深色/浅色主题中显式设置变量。
  8. 元素回调返回字符串——
    (context) => string
    返回类名,而非样式对象。如需样式对象请使用静态值。
  9. 上下文过滤为精确匹配——向收件箱传入
    context={{}}
    会隐藏所有使用非空上下文触发的通知,反之亦然。
  10. 不要在
    notification.data
    中存储敏感信息
    ——它会发送给客户端。
  11. renderNotification
    会移除默认操作
    ——如需保留标记为已读/归档/稍后提醒功能,请使用细粒度渲染属性(
    renderSubject
    renderBody
    renderAvatar
    renderDefaultActions
    renderCustomActions
    )。
  12. HTML渲染需完成两步——在工作流中禁用sanitization 在渲染属性中使用
    dangerouslySetInnerHTML
    。仅完成其中一步无效。

References

参考文档

  • Branding & Styling — full appearance API: themes, variables, elements, icons, severity, dynamic callbacks
  • Personalization — render props, custom popover (Radix, shadcn Drawer), conditional display, click handlers
  • Multi-Tenancy with Contexts — context-based isolation, securing contextHash, dynamic templates
  • React Inbox Examples
  • Next.js Inbox Examples
  • Headless Inbox (Vanilla JS)
  • Security (HMAC)
  • 品牌与样式——完整外观API:主题、变量、元素、图标、优先级、动态回调
  • 个性化定制——渲染属性、自定义弹窗(Radix、shadcn Drawer)、条件显示、点击处理
  • 基于上下文的多租户——基于上下文的隔离、contextHash安全、动态模板
  • React收件箱示例
  • Next.js收件箱示例
  • 无头收件箱(原生JS)
  • 安全(HMAC)