novu-inbox-integration について
Novuのアプリ内通知センターをWebアプリに追加するスキルです。React、Next.js、JavaScript向けのパッケージを使い、表示、通知操作、カスタマイズ、リアルタイム更新を扱います。
主な機能
- 01ReactとNext.jsでInboxを統合
- 02既読・アーカイブ操作付きフィードの表示
- 03テーマ、アイコン、外観のカスタマイズ
- 04通知表示とルーティングの個別設定
- 05通知のタブ分類
活用シーン
- 01Webアプリに通知センターを追加する
- 02製品テーマにInboxの外観を合わせる
- 03タグや重要度で通知を絞り込む
- 04通知選択時にユーザーを遷移させる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ novuhq/ skills --skill novu-inbox-integrationインストール後、Claude Code で/novu-inbox-integrationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/branding-and-styling.mdheadless-inbox-examples.mdmulti-tenancy.mdnextjs-inbox-examples.mdpersonalization.mdreact-inbox-examples.mdsecurity.md詳細
Inbox 統合
Web アプリケーションにアプリ内通知センターを追加します。Inbox コンポーネントには、ベルアイコン、通知フィード、既読/アーカイブ管理、アクションボタン、リアルタイムの WebSocket 更新が備わっており、製品に合わせてテーマやパーソナライズを設定できます。
パッケージ
| パッケージ | 用途 |
|---|---|
@novu/react | React 18/19 アプリケーション |
@novu/nextjs | Next.js(App Router + Pages Router) |
@novu/js | Vanilla 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 プロパティで完全にテーマ設定できます。次の 4 つのキーに対応しています。
| キー | 目的 |
|---|---|
baseTheme | 定義済みテーマ(例:dark)を適用 |
variables | グローバルデザイントークン(色、フォント、角丸、重大度の色) |
elements | 要素ごとのスタイル(スタイルオブジェクト、クラス文字列、またはコンテキストコールバック) |
icons | 組み込みアイコンを独自の React コンポーネントに置き換え |
スタイルは <head> に自動挿入されます(シャドウ DOM 内にレンダリングされる場合はシャドウルートに挿入されます)。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でレンダリングするには、次の手順に従います。
- ワークフローのIn-Appステップで、コンテンツのサニタイズを無効にするを無効にします。
- レンダープロパティで
dangerouslySetInnerHTMLを使ってレンダリングします。
<Inbox
/* ... */
renderBody={(notification) => (
<div dangerouslySetInnerHTML={{ __html: notification.body }} />
)}
renderSubject={(notification) => (
<span dangerouslySetInnerHTML={{ __html: notification.subject }} />
)}
/>
トリガーペイロードを完全に制御できる場合にのみ有効にしてください。生のHTMLはXSSの攻撃対象領域を広げます。
通知のクリック動作
受信トレイをルーターに接続します。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フックを使って、タブごとの未読件数を表示します。
コンテキストを使ったマルチテナンシー
コンテキストを使って、受信トレイの範囲をテナント、ワークスペース、または機能領域に限定します。受信トレイには、トリガーのコンテキストと受信トレイのコンテキストが完全に一致する通知のみが表示されます。
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
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}
/>
コンテキストの一致ルール
| ワークフローのコンテキスト | 受信トレイのコンテキスト | 表示される? |
|---|---|---|
{ tenant: "acme" } | { tenant: "acme" } | ✅ |
{} | {} | ✅ |
{ tenant: "acme" } | {} | ❌ |
{} | { tenant: "acme" } | ❌ |
{ tenant: "acme" } | { tenant: "globex" } | ❌ |
Novuにまだ存在しないコンテキストは自動的に作成されます。上書きを防ぐため、既存のコンテキストデータは自動更新されません。
セットアップ全体、ダッシュボードでの管理、{{context}}を使った動的コンテンツのレンダリングについては、マルチテナンシーのリファレンスを参照してください。
データオブジェクト
各In-Appステップでは、カスタムデータオブジェクトをサポートしています。ワークフローエディターで定義できるのは、最大10個のスカラー値のキーと値のペアです(string、number、boolean、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はクライアントに返されます。トリガーペイロード全体を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 の UI テキストを上書きします。多言語アプリや、製品のトーンに合わせる場合に便利です。
<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",
},
}}
/>
- ローカライズで変更されるのは UI テキストのみです。通知の内容を翻訳するには、ワークフローの翻訳を使用してください。
- Preferences UI に表示されるワークフロー名をローカライズするには、
dynamicマップを使用します。 - キーの全一覧は、
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 を生成してください(マルチテナンシーを参照)。
よくある落とし穴
applicationIdentifierはNOVU_SECRET_KEYとは異なります — アプリ ID はクライアント側で安全に使用できる公開識別子です。シークレットキーはサーバー側でのみ使用します。- 本番環境では HMAC ハッシュが必須です — これがないと、誰でもサブスクライバー ID を推測してなりすませます。
- Inbox に表示されるのは、
inAppステップを含むワークフローからの通知のみです — ワークフローにstep.inApp()が含まれていない場合、何も表示されません。 - Next.js App Router では
"use client"が必要です — Inbox コンポーネントはクライアント側でのみ動作します。 - リアルタイム更新は自動です — Inbox は内部で WebSocket を使用します。追加の設定は必要ありません。
@novu/reactと@novu/nextjs— Next.js アプリでは@novu/nextjsを使用してください(SSR のエッジケースに対応します)。その他の React アプリでは@novu/reactを使用してください。variablesはbaseThemeを上書きします —appearanceで両方が設定されている場合は、variables が優先されます。variables はダークテーマとライトテーマで意図的に設定してください。- 要素のコールバックは文字列を返します —
(context) => stringが返すのはクラス名であり、スタイルオブジェクトではありません。スタイルオブジェクトを使用する場合は、静的な値を指定してください。 - コンテキストによるフィルタリングは完全一致です — Inbox に
context={{}}を渡すと、空でないコンテキストでトリガーされた通知は非表示になります。逆の場合も同様です。 notification.dataにシークレットを保存しないでください — クライアントに送信されます。renderNotificationを使用するとデフォルトのアクションが削除されます — 既読、アーカイブ、スヌーズの操作を残すには、個別のレンダープロパティ(renderSubject、renderBody、renderAvatar、renderDefaultActions、renderCustomActions)を使用してください。- HTML のレンダリングには両方の手順が必要です — ワークフローでサニタイズを無効にし、レンダープロパティで
dangerouslySetInnerHTMLを使用します。どちらか一方だけでは効果がありません。
参考資料
- ブランディングとスタイル設定 — appearance API の全機能:テーマ、variables、elements、アイコン、重要度、動的コールバック
- パーソナライズ — レンダープロパティ、カスタムポップオーバー(Radix、shadcn Drawer)、条件付き表示、クリックハンドラー
- コンテキストを使用したマルチテナンシー — コンテキストベースの分離、contextHash の保護、動的テンプレート
- React Inbox の例
- Next.js Inbox の例
- ヘッドレス Inbox(Vanilla JS)
- セキュリティ(HMAC)