mcpField
N
novu-inbox-integration

novu-inbox-integration

github.com/novuhq开发4465 次安装19 周前更新
代码仓库

关于 novu-inbox-integration

用于将 Novu 站内通知中心集成到 Web 应用的技能,涵盖 React、Next.js 和 JavaScript 包,以及展示、通知操作、个性化和实时更新配置。

主要功能

  1. 01通过 React 和 Next.js 集成 Inbox
  2. 02展示通知并支持已读和归档操作
  3. 03自定义主题、图标和外观
  4. 04个性化通知渲染与路由
  5. 05将通知分组到标签页

适用场景

  1. 01为 Web 应用添加通知中心
  2. 02使 Inbox 样式匹配产品主题
  3. 03按标签或严重级别筛选通知
  4. 04选择通知时跳转到相应页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/novuhq/skills --skill novu-inbox-integration

安装后在 Claude Code 中输入/novu-inbox-integration确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
branding-and-styling.md
headless-inbox-examples.md
multi-tenancy.md
nextjs-inbox-examples.md
personalization.md
react-inbox-examples.md
security.md

详细介绍

收件箱集成

为你的 Web 应用添加应用内通知中心。Inbox 组件提供铃铛图标、通知动态、已读/归档管理、操作按钮和实时 WebSocket 更新,并支持主题化和个性化,以匹配你的产品。

软件包

软件包用途
@novu/reactReact 18/19 应用
@novu/nextjsNext.js(App Router + Pages Router)
@novu/js原生 JavaScript / 非 React 框架

React 快速入门

npm install @novu/react
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
    />
  );
}

这会渲染带有未读数量的铃铛图标。点击后会打开一个包含通知动态的弹出层。

Next.js

npm install @novu/nextjs
App Router
// 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
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"
    />
  );
}

可组合组件

<Inbox> 组件支持组合使用。传入子组件时,它会充当上下文提供程序,你可以通过基础组件组合 UI:

组件用途
<Bell />带有未读数量的铃铛图标
<Notifications />通知动态(页眉 + 列表 + 页脚)
<InboxContent />与 <Notifications /> 相同,另含偏好设置页面
<Preferences />独立的偏好设置面板
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>
  );
}

使用这些基础组件构建自定义弹出层、模态框、抽屉或全页通知体验。

Inbox 品牌化

Inbox 可通过 appearance 属性全面主题化。它支持四个键:

键用途
baseTheme应用预定义主题(例如 dark)
variables全局设计令牌(颜色、字体、圆角、严重程度颜色)
elements单个元素的样式(样式对象、类名字符串或上下文回调)
icons使用你自己的 React 组件替换内置图标

样式会自动注入到 <head>(若在 shadow DOM 内渲染,则注入到 shadow root)。同时提供 baseTheme 和 variables 时,以 variables 为准。

灵感来源:Inbox Playground 展示了 Notion 和 Reddit 等预设样式变体。

深色模式(及其他基础主题)
import { Inbox } from "@novu/react";
import { dark } from "@novu/react/themes";

<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  appearance={{ baseTheme: dark }}
/>
全局变量
<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",
    },
  }}
/>
元素级样式(Tailwind、CSS Modules、内联样式)

每个元素都接受类名字符串、样式对象,或用于运行时条件判断的函数 (context) => string。

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- 开头的类(在 DevTools 中 🔔 表情符号前可见)都对应 appearance.elements 中的一个键(去掉 nv- 前缀)。TS 自动补全会列出所有可用键。

自定义图标

通过从 appearance.icons 返回 React 组件来替换任何内置图标:

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。要查找更多图标,请检查以 nv- 开头且包含 🖼️ 表情符号的类。

严重程度样式

通知和铃铛会根据严重程度(high、medium、low)设置样式。通过 variables 覆盖颜色:

严重程度仅是一个视觉调节项。工作流级别的 critical: true 标志彼此独立——它会改变运行时投递行为(绕过偏好设置、跳过摘要),而不是 Inbox 样式。若 critical 工作流也需要在视觉上突出显示,应显式设置 severity: 'high'。有关完整设计规则,请参阅 design-workflow/references/severity-and-critical.md。

appearance: {
  variables: {
    colorSeverityHigh: "#E5484D",
    colorSeverityMedium: "#F76808",
    colorSeverityLow: "#3E63DD",
  },
}

……也可以按元素设置:

appearance: {
  elements: {
    severityHigh__notificationBar: { backgroundColor: "red" },
    severityHigh__bellContainer: "ring-2 ring-red-500",
    severityGlowHigh__bellSeverityGlow: "bg-red-500",
  },
}

默认情况下,铃铛会采用未读通知中最高严重程度对应的颜色。

响应式 Inbox
<Inbox
  /* ... */
  appearance={{ elements: { popoverContent: "novu-popover-content" } }}
/>
.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 风格预设,请参阅品牌化与样式参考。

个性化

渲染属性

覆盖通知的各个部分,同时保留周围的界面元素(操作按钮、悬停状态等):

<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——你需要自行重新实现默认操作(标记为已读、归档、稍后提醒)。

<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div className="custom-row">
      <h3>{notification.subject}</h3>
      <p>{notification.body}</p>
    </div>
  )}
/>
条件显示

renderNotification 会接收完整的通知——可根据 tags、data、severity 或 workflow.identifier 进行分支处理:

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

若要在 subject / body 中渲染富 HTML:

  1. 在工作流的 In-App 步骤中停用 Disable content sanitization。
  2. 在渲染属性中使用 dangerouslySetInnerHTML 进行渲染:
<Inbox
  /* ... */
  renderBody={(notification) => (
    <div dangerouslySetInnerHTML={{ __html: notification.body }} />
  )}
  renderSubject={(notification) => (
    <span dangerouslySetInnerHTML={{ __html: notification.subject }} />
  )}
/>

仅当你完全控制触发负载时才启用此功能——原始 HTML 会带来 XSS 攻击面。

通知点击行为

将 Inbox 接入路由器。Novu 会使用工作流中定义的 redirect.url 调用 routerPush:

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 模式,请参阅个性化参考。

标签页

按标签、严重程度或 data 属性将通知分组到标签页中:

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" } },
    },
  ]}
/>
  • 标签由工作流级别设置——在工作流编辑器中分配。多个标签使用 OR 逻辑。
  • 严重程度来自 In-App 步骤的严重程度设置(HIGH、MEDIUM、LOW)。
  • data 来自每个 In-App 步骤中定义的数据对象。

使用 useCounts hook显示每个标签页的未读数量。

使用上下文实现多租户

使用上下文将 Inbox 限定到租户、工作区或功能区域。Inbox 只会显示触发上下文与 Inbox 上下文完全匹配的通知。

1. 使用上下文触发工作流
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. 向 Inbox 传入匹配的上下文
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="user-123"
  subscriberHash="HMAC_HASH"
  context={{
    tenant: {
      id: "acme-corp",
      data: { name: "Acme Corporation", plan: "enterprise" },
    },
  }}
/>
3. 使用 contextHash 保护上下文

由于 context 在客户端设置,恶意用户可能会替换租户 ID。在服务器端生成规范化上下文的 HMAC 哈希:

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 一同传入:

<Inbox
  /* ... */
  context={context}
  contextHash={contextHash}
/>
上下文匹配规则
工作流上下文Inbox 上下文是否显示?
{ tenant: "acme" }{ tenant: "acme" }✅
{}{}✅
{ tenant: "acme" }{}❌
{}{ tenant: "acme" }❌
{ tenant: "acme" }{ tenant: "globex" }❌

Novu 会自动创建尚不存在的上下文。为防止覆盖,现有上下文数据不会自动更新。

完整设置说明、仪表板管理以及使用 {{context}} 渲染动态内容,请参阅多租户参考。

数据对象

每个 In-App 步骤都支持自定义数据对象——最多包含 10 个标量键值对(字符串、数字、布尔值、null;字符串长度 ≤ 256 个字符),在工作流编辑器中定义。值可以是静态的("status": "merged"),也可以是动态的("firstName": "{{subscriber.firstName}}")。

可在客户端通过 notification.data 访问,并用于渲染决策、条件样式和标签页筛选。

<Inbox
  /* ... */
  renderNotification={(notification) => (
    <div>
      <span>{notification.data?.emoji}</span>
      <strong>{notification.data?.firstName}</strong>
      <p>{notification.body}</p>
    </div>
  )}
/>

为数据对象指定全局类型,以启用自动补全:

declare global {
  interface NotificationData {
    reactionType?: string;
    entityId?: string;
    userName?: string;
  }
}

不要在 data 中存储机密——它会返回给客户端。切勿将整个触发负载展开到 data 中。

自定义弹出层

将通知动态流嵌入任意弹出层、抽屉或页面布局中。使用 <Bell />(或你自己的触发器)搭配 <Notifications /> 或 <InboxContent />:

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> 提供程序传递。

本地化

覆盖 Inbox 界面文本,适用于多语言应用或与产品语气保持一致:

<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",
    },
  }}
/>
  • 本地化仅更改界面文本。要翻译通知内容,请使用工作流翻译。
  • 使用 dynamic 映射本地化 Preferences 界面中显示的工作流名称。
  • 完整的键列表见 defaultLocalization.ts。

HMAC 身份验证

生产环境中必须启用,以防止订阅者身份冒用。完整指南请参阅 https://docs.novu.co/platform/inbox/prepare-for-production。

生成哈希(服务端)
import { createHmac } from "crypto";

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

subscriber_hash = hmac.new(
    NOVU_SECRET_KEY.encode(),
    subscriber_id.encode(),
    hashlib.sha256,
).hexdigest()
传递给组件
<Inbox
  applicationIdentifier="YOUR_NOVU_APP_ID"
  subscriberId="subscriber-123"
  subscriberHash={subscriberHash}
/>

如果还传入了 context,请生成 contextHash(参见使用 Context 的多租户)。

常见问题

  1. applicationIdentifier 与 NOVU_SECRET_KEY 不同——应用 ID 是可安全用于客户端的公开标识符。密钥仅限服务端使用。
  2. 生产环境中必须使用 HMAC 哈希——没有它,任何人都可以通过猜测订阅者 ID 来冒充订阅者。
  3. Inbox 只显示来自包含 inApp 步骤的工作流的通知——如果工作流不包含 step.inApp(),就不会显示任何内容。
  4. 在 Next.js App Router 中必须使用 "use client"——Inbox 组件仅在客户端运行。
  5. 实时更新会自动进行——Inbox 内部使用 WebSocket。无需额外设置。
  6. @novu/react 与 @novu/nextjs——Next.js 应用使用 @novu/nextjs(可处理 SSR 边缘情况),其他 React 应用使用 @novu/react。
  7. variables 会覆盖 baseTheme——当两者都在 appearance 中设置时,以 variables 为准。请有意在深色/浅色主题中设置 variables。
  8. 元素回调返回字符串——(context) => string 返回的是类名,而非样式对象。要使用样式对象,请使用静态值。
  9. Context 过滤采用精确匹配——向 Inbox 传入 context={{}} 会隐藏任何由非空 context 触发的通知,反之亦然。
  10. 不要在 notification.data 中存储密钥——它会发送到客户端。
  11. renderNotification 会移除默认操作——如果要保留标记为已读/归档/延后等操作,请使用细粒度的渲染属性(renderSubject、renderBody、renderAvatar、renderDefaultActions、renderCustomActions)。
  12. 渲染 HTML 需要完成两个步骤——在工作流中禁用清理,并且在渲染属性中使用 dangerouslySetInnerHTML。只做其中一步不会生效。

参考资料

评论

支持 Markdown