novu-inbox-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInbox 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
包列表
| Package | Use For |
|---|---|
| React 18/19 applications |
| Next.js (App Router + Pages Router) |
| Vanilla JavaScript / non-React frameworks |
| 包名 | 适用场景 |
|---|---|
| React 18/19 应用 |
| Next.js(App Router + Pages Router) |
| 原生JavaScript / 非React框架 |
React Quick Start
React快速开始
bash
npm install @novu/reacttsx
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/reacttsx
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/nextjsbash
npm install @novu/nextjsApp 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 directive in Next.js App Router.
"use client"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 component is composable. When you pass children, it acts as a context provider and you compose the UI from primitives:
<Inbox>| Component | Purpose |
|---|---|
| Bell icon with unread count |
| Notification feed (header + list + footer) |
| Same as |
| 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>| 组件 | 用途 |
|---|---|
| 带有未读计数的铃铛图标 |
| 通知流(头部+列表+底部) |
| 包含偏好设置页面的完整通知流 |
| 独立的偏好设置面板 |
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 prop. It supports four keys:
appearance| Key | Purpose |
|---|---|
| Apply a predefined theme (e.g. |
| Global design tokens (colors, fonts, radius, severity colors) |
| Per-element styles (style object, class string, or context callback) |
| Replace built-in icons with your own React components |
Styles are auto-injected into (or the shadow root if rendered inside a shadow DOM). When both and are provided, win.
<head>baseThemevariablesvariablesInspiration: the Inbox Playground showcases pre-styled variants like Notion and Reddit.
通过属性可对收件箱进行完全主题定制,支持以下四个配置项:
appearance| 配置项 | 用途 |
|---|---|
| 应用预定义主题(如 |
| 全局设计令牌(颜色、字体、圆角、优先级颜色) |
| 元素级样式(样式对象、类名或上下文回调函数) |
| 使用自定义React组件替换内置图标 |
样式会自动注入到中(若在Shadow DOM内渲染则注入到Shadow根节点)。当同时提供和时,的配置优先级更高。
<head>baseThemevariablesvariables灵感来源:收件箱演示 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 for runtime conditionals.
(context) => stringtsx
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(visible just before a 🔔 emoji in DevTools) maps to a key innv-(drop theappearance.elementsprefix). TS autocomplete lists all available keys.nv-
每个元素支持传入类名字符串、样式对象或运行时条件判断函数。
(context) => stringtsx
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:所有以开头的类名(在开发者工具中🔔 emoji前可见)对应nv-中的键(去掉appearance.elements前缀)。TypeScript自动补全会列出所有可用键。nv-
Custom icons
自定义图标
Replace any built-in icon by returning a React component from :
appearance.iconstsx
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: , , , , , , , , , , , , , , , , , , . To find more, inspect classes that start with and contain a 🖼️ emoji.
bellcogsdotsarrowDownarrowDropDownarrowLeftarrowRightcheckclocktrashmarkAsReadmarkAsUnreadmarkAsArchivedmarkAsUnarchivedemailsmspushinAppchatnv-通过返回React组件替换任意内置图标:
appearance.iconstsx
import { RiSettings3Fill, RiNotification3Fill } from "react-icons/ri";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
appearance={{
icons: {
bell: () => <RiNotification3Fill />,
cogs: () => <RiSettings3Fill />,
},
}}
/>常见图标键:、、、、、、、、、、、、、、、、、、。如需查找更多,可检查包含🖼️ emoji的开头类名。
bellcogsdotsarrowDownarrowDropDownarrowLeftarrowRightcheckclocktrashmarkAsReadmarkAsUnreadmarkAsArchivedmarkAsUnarchivedemailsmspushinAppchatnv-Severity styling
优先级样式
Notifications and the bell are styled by severity (, , ). Override colors via :
highmediumlowvariablesSeverity is a visual dial only. The workflow-levelflag is independent — it changes runtime delivery (bypass preferences, skip digest), not Inbox styling.critical: trueworkflows that should also stand out visually should setcriticalexplicitly. Seeseverity: 'high'for the full design rules.design-workflow/references/severity-and-critical.md
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.
通知和铃铛会根据优先级(、、)设置样式。可通过覆盖颜色:
highmediumlowvariables优先级仅为视觉标识。工作流层面的标记是独立的——它会改变运行时推送逻辑(绕过偏好设置、跳过摘要),但不会影响收件箱样式。若需关键工作流在视觉上突出显示,需显式设置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 only when you need full control of the item — you'll need to re-implement default actions (mark as read, archive, snooze) yourself.
renderNotificationtsx
<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} />
)}
/>仅当需要完全控制通知项时使用——此时需自行实现默认操作(标记为已读、归档、稍后提醒)。
renderNotificationtsx
<Inbox
/* ... */
renderNotification={(notification) => (
<div className="custom-row">
<h3>{notification.subject}</h3>
<p>{notification.body}</p>
</div>
)}
/>Conditional display
条件显示
renderNotificationtagsdataseverityworkflow.identifiertsx
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} />;
}}renderNotificationtagsdataseverityworkflow.identifiertsx
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 / :
subjectbody- Disable Disable content sanitization in the In-App step in your workflow.
- Render with in a render prop:
dangerouslySetInnerHTML
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.
如需在/中渲染富HTML:
subjectbody- 在工作流的应用内步骤中禁用禁用内容 sanitization。
- 在渲染属性中使用:
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 with the defined in your workflow:
routerPushredirect.urltsx
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 (), Remix (), Gatsby (), and any custom router.
useNavigate()useNavigate()navigate()See Personalization Reference for full render-prop signatures, styling examples, popover composition with Radix / shadcn Drawer, and conditional UI patterns.
renderCustomActions将收件箱与你的路由集成。Novu会调用并传入工作流中定义的:
routerPushredirect.urltsx
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()、Remix()、Gatsby()以及任何自定义路由。
useNavigate()useNavigate()navigate()完整渲染属性签名、样式示例、与Radix/shadcn Drawer的弹窗组合以及条件UI模式,请参考个性化定制参考文档。
renderCustomActionsTabs
标签页
Group notifications into tabs by tags, severity, or properties:
datatsx
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 logic.
OR - Severity comes from the In-App step's severity setting (,
HIGH,MEDIUM).LOW - comes from the data object defined per In-App step.
data
Use the hook to render unread counts per tab.
useCounts通过标签、优先级或**属性**将通知分组到标签页:
datatsx
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
使用 hook渲染每个标签页的未读计数。
useCountsMulti-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
contextHash3. 使用contextHash
保护上下文
contextHashBecause is set client-side, a hostile user could swap tenant IDs. Generate an HMAC hash of the canonicalized context server-side:
contexttypescript
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 :
contexttsx
<Inbox
/* ... */
context={context}
contextHash={contextHash}
/>由于在客户端设置,恶意用户可能会替换租户ID。在服务端生成规范化上下文的HMAC哈希:
contexttypescript
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");将其与一起传入:
contexttsx
<Inbox
/* ... */
context={context}
contextHash={contextHash}
/>Context match rules
上下文匹配规则
| Workflow Context | Inbox Context | Displayed? |
|---|---|---|
| | ✅ |
| | ✅ |
| | ❌ |
| | ❌ |
| | ❌ |
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}}| 工作流上下文 | 收件箱上下文 | 是否显示? |
|---|---|---|
| | ✅ |
| | ✅ |
| | ❌ |
| | ❌ |
| | ❌ |
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 () or dynamic ().
"status": "merged""firstName": "{{subscriber.firstName}}"Access it client-side as and use it for render decisions, conditional styling, and tab filtering.
notification.datatsx
<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— it's returned to the client. Never spread the entire trigger payload intodata.data
每个应用内步骤支持自定义数据对象——最多10个标量键值对(字符串、数字、布尔值、null;字符串长度≤256字符),在工作流编辑器中定义。值可以是静态的()或动态的()。
"status": "merged""firstName": "{{subscriber.firstName}}"在客户端可通过访问该对象,并用于渲染决策、条件样式和标签页过滤。
notification.datatsx
<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 (or your own trigger) plus or :
<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>The same pattern works with shadcn , Headless UI, or a route-level page (mount directly without any popover). All customization props (, , , , render props) flow through the provider.
<Drawer><InboxContent />appearancelocalizationtabsrouterPush<Inbox>将通知流挂载到任意弹窗、侧边栏或页面布局中。使用(或自定义触发器)搭配或:
<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 、Headless UI或路由级页面(直接挂载无需弹窗)。所有自定义属性(、、、、渲染属性)都会通过提供者传递。
<Drawer><InboxContent />appearancelocalizationtabsrouterPush<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 map to localize workflow names shown in the Preferences UI.
dynamic - 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文本。如需翻译通知内容,请使用工作流翻译。
- 使用映射本地化偏好设置UI中显示的工作流名称。
dynamic - 完整键列表请参考。
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}
/>tsx
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
subscriberHash={subscriberHash}
/>Common Pitfalls
常见陷阱
- is NOT the same as
applicationIdentifier— the app ID is a public identifier safe for client-side use. The secret key is server-only.NOVU_SECRET_KEY - HMAC hash is mandatory in production — without it, anyone can impersonate a subscriber by guessing their ID.
- The Inbox only shows notifications from workflows with an step — if your workflow doesn't include
inApp, nothing appears.step.inApp() - is required in Next.js App Router — the Inbox component is client-side only.
"use client" - Real-time updates are automatic — the Inbox uses WebSockets internally. No additional setup needed.
- vs
@novu/react— use@novu/nextjsfor Next.js apps (handles SSR edge cases),@novu/nextjsfor all other React apps.@novu/react - override
variables— when both are set inbaseTheme, variables win. Set variables in dark/light themes intentionally.appearance - Element callbacks return strings — returns class names, not style objects. For style objects use a static value.
(context) => string - Context filtering is exact-match — passing to the Inbox hides any notification triggered with a non-empty context, and vice-versa.
context={{}} - Don't store secrets in — it's sent to the client.
notification.data - removes default actions — use granular render props (
renderNotification,renderSubject,renderBody,renderAvatar,renderDefaultActions) when you want to keep mark-as-read / archive / snooze affordances.renderCustomActions - HTML rendering requires both steps — disabling sanitization in the workflow and using in a render prop. Either alone has no effect.
dangerouslySetInnerHTML
- 与
applicationIdentifier不同——应用ID是公开标识符,可安全用于客户端。密钥仅用于服务端。NOVU_SECRET_KEY - 生产环境必须使用HMAC哈希——否则任何人都可以通过猜测订阅者ID冒充他人。
- 收件箱仅显示包含步骤的工作流通知——若工作流未包含
inApp,则不会显示任何内容。step.inApp() - Next.js App Router中必须使用——Inbox组件仅支持客户端。
"use client" - 实时更新自动生效——收件箱内部使用WebSocket,无需额外设置。
- vs
@novu/react——Next.js应用使用@novu/nextjs(处理SSR边缘情况),其他React应用使用@novu/nextjs。@novu/react - 覆盖
variables——当baseTheme中同时设置两者时,appearance优先级更高。需在深色/浅色主题中显式设置变量。variables - 元素回调返回字符串——返回类名,而非样式对象。如需样式对象请使用静态值。
(context) => string - 上下文过滤为精确匹配——向收件箱传入会隐藏所有使用非空上下文触发的通知,反之亦然。
context={{}} - 不要在中存储敏感信息——它会发送给客户端。
notification.data - 会移除默认操作——如需保留标记为已读/归档/稍后提醒功能,请使用细粒度渲染属性(
renderNotification、renderSubject、renderBody、renderAvatar、renderDefaultActions)。renderCustomActions - 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)