Back to Novu

Agent Skills

docs/platform/build-with-ai/skills.mdx

3.18.032.0 KB
Original Source

What are Agent Skills?

Agent Skills are an open standard that gives AI agents (Claude Code, Cursor, Copilot, etc.) the context they need to work with specific tools and platforms. Novu's skills provide AI agents with everything required to build multi-channel notification systems — triggering workflows, managing subscribers, integrating the in-app inbox, and configuring preferences.

Prerequisites

Setup

Install the Novu skills into your project using the skills CLI:

bash
npx skills add novuhq/skills

This pulls the skills from the novuhq/skills GitHub repository and makes them available to your AI agent (Claude Code, Cursor, Copilot, etc.).


Available Skills

SkillDescription
trigger-notificationSend single, bulk, broadcast, and topic-based notifications
manage-subscribersCRUD operations on subscribers and topics
inbox-integrationIntegrate the in-app notification inbox into React, Next.js, or vanilla JS
manage-preferencesConfigure workflow and subscriber notification preferences

Quick Routing

Use this guide to pick the right skill for your task:

  • "Send a welcome notification"trigger-notification
  • "Create subscriber"manage-subscribers
  • "Add a bell icon to my app"inbox-integration
  • "Let users opt out of emails"manage-preferences

Example prompts

Copy or open these prompts in Cursor after installing Novu skills with npx skills add novuhq/skills. Replace YOUR_* placeholders with values from your Novu dashboard.

Core skills

<Prompt description="Send a welcome notification" icon="bot" actions={["copy", "cursor"]}> Send a welcome notification to subscriber-123 using the welcome-email workflow. Use @novu/api with NOVU_SECRET_KEY from environment variables.

Example payload:

json
{
  "userName": "Jane",
  "activationLink": "https://app.example.com/activate"
}

Follow the novu-trigger-notification skill. Do not create documentation files — implement the trigger in my existing codebase. </Prompt>

<Prompt description="Create a subscriber" icon="bot" actions={["copy", "cursor"]}> Create a subscriber with subscriberId user-456, email [email protected], and firstName Jane in my Novu account using @novu/api.

Follow the novu-manage-subscribers skill. Store NOVU_SECRET_KEY in environment variables. </Prompt>

<Prompt description="Add a bell icon to my app" icon="bot" actions={["copy", "cursor"]}> Add a notification bell (Novu Inbox) to my Next.js app navbar. Install @novu/nextjs, use applicationIdentifier YOUR_APPLICATION_IDENTIFIER and subscriberId from my auth system.

Follow the novu-inbox-integration skill. Place the Inbox inline in existing UI — no new pages or wrappers.

Latest docs: https://docs.novu.co/platform/quickstart/nextjs </Prompt>

<Prompt description="Configure subscriber preferences" icon="bot" actions={["copy", "cursor"]}> Let subscriber-123 opt out of marketing email notifications while keeping transactional emails enabled.

Follow the novu-manage-preferences skill using @novu/api with NOVU_SECRET_KEY from environment variables. </Prompt>

Additional skills

These skills are available in the Novu documentation MCP and Mintlify-hosted skill packages:

<Prompt description="Set up a code-first workflow" icon="bot" actions={["copy", "cursor"]}> Set up a code-first Novu workflow using @novu/framework in my Next.js app. Create a welcome-email workflow with an email step and show me how to trigger it.

Follow the novu-framework-integration skill. Use NOVU_SECRET_KEY from environment variables.

Latest docs: https://docs.novu.co/framework/quickstart/nextjs </Prompt>

<Prompt description="Design a multi-channel workflow" icon="bot" actions={["copy", "cursor"]}> Help me design a multi-channel notification workflow for order-shipped events. Include in-app, email, and optional SMS steps with appropriate conditions.

Follow the novu-design-workflow skill. Ask clarifying questions about my channels and subscriber preferences before proposing the workflow structure. </Prompt>

<Prompt description="Configure workflow step content" icon="bot" actions={["copy", "cursor"]}> Configure the email step in my welcome-email workflow: set the subject to "Welcome, [payload userName]!" and add a body with the activation link from the payload activationLink field.

Follow the novu-dashboard-workflows skill. Use Liquid variables for personalization. </Prompt>

Common Combinations

  • Full notification system: trigger-notification + manage-subscribers
  • In-app notifications: trigger-notification + inbox-integration
  • Complete stack: all four skills together

SDK Overview

PackageSidePurpose
@novu/apiServerTrigger notifications, manage subscribers/topics/workflows via REST
@novu/reactClientReact Inbox component, Notifications, Preferences, Bell
@novu/nextjsClientNext.js-optimized Inbox integration
@novu/react-nativeClientReact Native hooks-based Inbox integration
@novu/jsClientVanilla JavaScript client for non-React apps

Skill Reference

Trigger Notification

Send notifications by triggering Novu workflows. Supports single, bulk, broadcast, and topic-based delivery.

Single trigger

<Tabs> <Tab title="Node.js"> ```typescript import { Novu } from "@novu/api";

const novu = new Novu({ secretKey: process.env.NOVU_SECRET_KEY });

await novu.trigger({ workflowId: "welcome-email", to: "subscriber-123", payload: { userName: "Jane", activationLink: "https://app.example.com/activate", }, });

</Tab>
<Tab title="Python">
```python
import os
import novu_py
from novu_py import Novu

with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
    novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
        workflow_id="welcome-email",
        to="subscriber-123",
        payload={
            "userName": "Jane",
            "activationLink": "https://app.example.com/activate",
        },
    ))
</Tab> <Tab title="Go"> ```go import ( "context" "os"
novugo "github.com/novuhq/novu-go"
"github.com/novuhq/novu-go/models/components"

)

s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

_, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{ WorkflowID: "welcome-email", To: components.CreateToStr("subscriber-123"), Payload: map[string]any{ "userName": "Jane", "activationLink": "https://app.example.com/activate", }, }, nil)

</Tab>
<Tab title="PHP">
```php
use novu;
use novu\Models\Components;

$sdk = novu\Novu::builder()
    ->setSecurity('<YOUR_SECRET_KEY_HERE>')
    ->build();

$sdk->trigger(
    triggerEventRequestDto: new Components\TriggerEventRequestDto(
        workflowId: 'welcome-email',
        to: 'subscriber-123',
        payload: [
            'userName' => 'Jane',
            'activationLink' => 'https://app.example.com/activate',
        ],
    ),
);
</Tab> <Tab title=".NET"> ```csharp using Novu; using Novu.Models.Components; using System.Collections.Generic;

var sdk = new NovuSDK(secretKey: "<YOUR_SECRET_KEY_HERE>");

await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() { WorkflowId = "welcome-email", To = To.CreateStr("subscriber-123"), Payload = new Dictionary<string, object>() { { "userName", "Jane" }, { "activationLink", "https://app.example.com/activate" }, }, });

</Tab>
<Tab title="Java">
```java
import co.novu.Novu;
import co.novu.models.components.*;
import java.util.Map;

Novu novu = Novu.builder()
    .secretKey("<YOUR_SECRET_KEY_HERE>")
    .build();

novu.trigger()
    .body(TriggerEventRequestDto.builder()
        .workflowId("welcome-email")
        .to(To2.of("subscriber-123"))
        .payload(Map.ofEntries(
            Map.entry("userName", "Jane"),
            Map.entry("activationLink", "https://app.example.com/activate")))
        .build())
    .call();
</Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v1/events/trigger' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "name": "welcome-email", "to": "subscriber-123", "payload": { "userName": "Jane", "activationLink": "https://app.example.com/activate" } }' ``` </Tab> </Tabs>

Bulk trigger

Send up to 100 events in a single request:

<Tabs> <Tab title="Node.js"> ```typescript await novu.triggerBulk({ events: [ { workflowId: "welcome-email", to: "subscriber-1", payload: { userName: "Alice" } }, { workflowId: "welcome-email", to: "subscriber-2", payload: { userName: "Bob" } }, ], }); ``` </Tab> <Tab title="Python"> ```python novu.trigger_bulk(trigger_bulk_request_dto=novu_py.TriggerBulkRequestDto( events=[ {"workflow_id": "welcome-email", "to": "subscriber-1", "payload": {"userName": "Alice"}}, {"workflow_id": "welcome-email", "to": "subscriber-2", "payload": {"userName": "Bob"}}, ], )) ``` </Tab> <Tab title="Go"> ```go _, err := s.TriggerBulk(ctx, components.TriggerBulkRequestDto{ Events: []components.TriggerEventRequestDto{ {WorkflowID: "welcome-email", To: components.CreateToStr("subscriber-1"), Payload: map[string]any{"userName": "Alice"}}, {WorkflowID: "welcome-email", To: components.CreateToStr("subscriber-2"), Payload: map[string]any{"userName": "Bob"}}, }, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->triggerBulk(triggerBulkRequestDto: new Components\TriggerBulkRequestDto( events: [ new Components\TriggerEventRequestDto(workflowId: 'welcome-email', to: 'subscriber-1', payload: ['userName' => 'Alice']), new Components\TriggerEventRequestDto(workflowId: 'welcome-email', to: 'subscriber-2', payload: ['userName' => 'Bob']), ], )); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.TriggerBulkAsync(new TriggerBulkRequestDto { Events = new List<TriggerEventRequestDto> { new() { WorkflowId = "welcome-email", To = To.CreateStr("subscriber-1"), Payload = new Dictionary<string, object>() { { "userName", "Alice" } } }, new() { WorkflowId = "welcome-email", To = To.CreateStr("subscriber-2"), Payload = new Dictionary<string, object>() { { "userName", "Bob" } } }, }, }); ``` </Tab> <Tab title="Java"> ```java novu.triggerBulk().body(TriggerBulkRequestDto.builder() .events(List.of( TriggerEventRequestDto.builder().workflowId("welcome-email").to(To2.of("subscriber-1")).payload(Map.of("userName", "Alice")).build(), TriggerEventRequestDto.builder().workflowId("welcome-email").to(To2.of("subscriber-2")).payload(Map.of("userName", "Bob")).build())) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v1/events/trigger/bulk' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "events": [ { "name": "welcome-email", "to": "subscriber-1", "payload": { "userName": "Alice" } }, { "name": "welcome-email", "to": "subscriber-2", "payload": { "userName": "Bob" } } ] }' ``` </Tab> </Tabs>

Broadcast trigger

Send to all subscribers in the environment:

<Tabs> <Tab title="Node.js"> ```typescript await novu.triggerBroadcast({ name: "system-announcement", payload: { message: "Scheduled maintenance at 2am UTC" }, }); ``` </Tab> <Tab title="Python"> ```python novu.trigger_broadcast(trigger_broadcast_request_dto=novu_py.TriggerBroadcastRequestDto( name="system-announcement", payload={"message": "Scheduled maintenance at 2am UTC"}, )) ``` </Tab> <Tab title="Go"> ```go _, err := s.TriggerBroadcast(ctx, components.TriggerBroadcastRequestDto{ Name: "system-announcement", Payload: map[string]any{"message": "Scheduled maintenance at 2am UTC"}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->triggerBroadcast(triggerBroadcastRequestDto: new Components\TriggerBroadcastRequestDto( name: 'system-announcement', payload: ['message' => 'Scheduled maintenance at 2am UTC'], )); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.TriggerBroadcastAsync(new TriggerBroadcastRequestDto { Name = "system-announcement", Payload = new Dictionary<string, object>() { { "message", "Scheduled maintenance at 2am UTC" } }, }); ``` </Tab> <Tab title="Java"> ```java novu.triggerBroadcast().body(TriggerBroadcastRequestDto.builder() .name("system-announcement") .payload(Map.of("message", "Scheduled maintenance at 2am UTC")) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v1/events/trigger/broadcast' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "name": "system-announcement", "payload": { "message": "Scheduled maintenance at 2am UTC" } }' ``` </Tab> </Tabs>

Topic trigger

Send to all subscribers in a topic:

<Tabs> <Tab title="Node.js"> ```typescript await novu.trigger({ workflowId: "project-update", to: [{ type: "Topic", topicKey: "project-alpha-watchers" }], payload: { update: "New release deployed" }, }); ``` </Tab> <Tab title="Python"> ```python novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto( workflow_id="project-update", to=[{"type": "Topic", "topic_key": "project-alpha-watchers"}], payload={"update": "New release deployed"}, )) ``` </Tab> <Tab title="Go"> ```go _, err := s.Trigger(ctx, components.TriggerEventRequestDto{ WorkflowID: "project-update", To: components.CreateToArrayOfTo1([]components.To1{ components.CreateTo1TopicPayloadDto(components.TopicPayloadDto{ Type: components.TriggerRecipientsTypeEnumTopic, TopicKey: "project-alpha-watchers", }), }), Payload: map[string]any{"update": "New release deployed"}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->trigger(triggerEventRequestDto: new Components\TriggerEventRequestDto( workflowId: 'project-update', to: [new Components\TopicPayloadDto( topicKey: 'project-alpha-watchers', type: Components\TriggerRecipientsTypeEnum::Topic, )], payload: ['update' => 'New release deployed'], )); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.TriggerAsync(new TriggerEventRequestDto { WorkflowId = "project-update", To = To.CreateArrayOfTo1(new List<To1> { To1.CreateTopicPayloadDto(new TopicPayloadDto { Type = TriggerRecipientsTypeEnum.Topic, TopicKey = "project-alpha-watchers", }), }), Payload = new Dictionary<string, object>() { { "update", "New release deployed" } }, }); ``` </Tab> <Tab title="Java"> ```java novu.trigger().body(TriggerEventRequestDto.builder() .workflowId("project-update") .to(To2.of(List.of(To1.of(TopicPayloadDto.builder() .type(TriggerRecipientsTypeEnum.TOPIC) .topicKey("project-alpha-watchers") .build())))) .payload(Map.of("update", "New release deployed")) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v1/events/trigger' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "name": "project-update", "to": [{ "type": "Topic", "topicKey": "project-alpha-watchers" }], "payload": { "update": "New release deployed" } }' ``` </Tab> </Tabs>

Key trigger parameters:

ParameterRequiredDescription
workflowIdYesThe workflow identifier (not the display name)
toYesSubscriber ID string, subscriber object, or topic target
payloadNoData passed to the workflow, validated against payloadSchema
overridesNoProvider-specific overrides per channel
transactionIdNoUnique ID for idempotency and cancellation
actorNoSubscriber representing who triggered the action
contextNoKey-value pairs for multi-tenancy / organizational context

Manage Subscribers

Subscribers are the recipients of your notifications. Each subscriber has a unique subscriberId — typically your application's user ID.

Create

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.create({ subscriberId: "user-123", email: "[email protected]", firstName: "Jane", lastName: "Doe", phone: "+15551234567", data: { plan: "pro" }, }); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.create( create_subscriber_request_dto=novu_py.CreateSubscriberRequestDto( subscriber_id="user-123", email="[email protected]", first_name="Jane", last_name="Doe", phone="+15551234567", data={"plan": "pro"}, ) ) ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.Create(ctx, components.CreateSubscriberRequestDto{ SubscriberID: "user-123", Email: "[email protected]", FirstName: "Jane", LastName: "Doe", Phone: "+15551234567", Data: map[string]any{"plan": "pro"}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->create( createSubscriberRequestDto: new Components\CreateSubscriberRequestDto( subscriberId: 'user-123', email: '[email protected]', firstName: 'Jane', lastName: 'Doe', phone: '+15551234567', data: ['plan' => 'pro'], ), ); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.CreateAsync(new CreateSubscriberRequestDto { SubscriberId = "user-123", Email = "[email protected]", FirstName = "Jane", LastName = "Doe", Phone = "+15551234567", Data = new Dictionary<string, object>() { { "plan", "pro" } }, }); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().create() .body(CreateSubscriberRequestDto.builder() .subscriberId("user-123") .email("[email protected]") .firstName("Jane") .lastName("Doe") .phone("+15551234567") .data(Map.of("plan", "pro")) .build()) .call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v2/subscribers' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "subscriberId": "user-123", "email": "[email protected]", "firstName": "Jane", "lastName": "Doe", "phone": "+15551234567", "data": { "plan": "pro" } }' ``` </Tab> </Tabs>

Retrieve

<Tabs> <Tab title="Node.js"> ```typescript const subscriber = await novu.subscribers.retrieve("user-123"); ``` </Tab> <Tab title="Python"> ```python subscriber = novu.subscribers.retrieve(subscriber_id="user-123") ``` </Tab> <Tab title="Go"> ```go res, err := s.Subscribers.Get(ctx, "user-123", nil) ``` </Tab> <Tab title="PHP"> ```php $response = $sdk->subscribers->get(subscriberId: 'user-123'); ``` </Tab> <Tab title=".NET"> ```csharp var subscriber = await sdk.Subscribers.GetAsync("user-123"); ``` </Tab> <Tab title="Java"> ```java var subscriber = novu.subscribers().get("user-123").call(); ``` </Tab> <Tab title="cURL"> ```bash curl 'https://api.novu.co/v2/subscribers/user-123' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' ``` </Tab> </Tabs>

Update

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.patch( { firstName: "Jane", data: { plan: "enterprise" } }, "user-123" ); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.patch( subscriber_id="user-123", patch_subscriber_request_dto={"first_name": "Jane", "data": {"plan": "enterprise"}}, ) ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.Patch(ctx, "user-123", components.PatchSubscriberRequestDto{ FirstName: novu.String("Jane"), Data: map[string]any{"plan": "enterprise"}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->patch('user-123', new Components\PatchSubscriberRequestDto( firstName: 'Jane', data: ['plan' => 'enterprise'], )); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.PatchAsync("user-123", new PatchSubscriberRequestDto { FirstName = "Jane", Data = new Dictionary<string, object>() { { "plan", "enterprise" } }, }); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().patch("user-123").body(PatchSubscriberRequestDto.builder() .firstName("Jane") .data(Map.of("plan", "enterprise")) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X PATCH 'https://api.novu.co/v2/subscribers/user-123' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "firstName": "Jane", "data": { "plan": "enterprise" } }' ``` </Tab> </Tabs>

Delete

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.delete("user-123"); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.delete(subscriber_id="user-123") ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.Delete(ctx, "user-123", nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->delete(subscriberId: 'user-123'); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.DeleteAsync("user-123"); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().delete("user-123").call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X DELETE 'https://api.novu.co/v2/subscribers/user-123' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' ``` </Tab> </Tabs>

Bulk create

Create up to 500 subscribers in one request:

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.createBulk({ subscribers: [ { subscriberId: "user-1", email: "[email protected]", firstName: "Alice" }, { subscriberId: "user-2", email: "[email protected]", firstName: "Bob" }, ], }); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.create_bulk( bulk_subscriber_create_dto=novu_py.BulkSubscriberCreateDto( subscribers=[ {"subscriber_id": "user-1", "email": "[email protected]", "first_name": "Alice"}, {"subscriber_id": "user-2", "email": "[email protected]", "first_name": "Bob"}, ], ) ) ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.CreateBulk(ctx, components.BulkSubscriberCreateDto{ Subscribers: []components.CreateSubscriberRequestDto{ {SubscriberID: "user-1", Email: "[email protected]", FirstName: "Alice"}, {SubscriberID: "user-2", Email: "[email protected]", FirstName: "Bob"}, }, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->createBulk(bulkSubscriberCreateDto: new Components\BulkSubscriberCreateDto( subscribers: [ new Components\CreateSubscriberRequestDto(subscriberId: 'user-1', email: '[email protected]', firstName: 'Alice'), new Components\CreateSubscriberRequestDto(subscriberId: 'user-2', email: '[email protected]', firstName: 'Bob'), ], )); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.CreateBulkAsync(new BulkSubscriberCreateDto { Subscribers = new List<CreateSubscriberRequestDto> { new() { SubscriberId = "user-1", Email = "[email protected]", FirstName = "Alice" }, new() { SubscriberId = "user-2", Email = "[email protected]", FirstName = "Bob" }, }, }); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().createBulk().body(BulkSubscriberCreateDto.builder() .subscribers(List.of( CreateSubscriberRequestDto.builder().subscriberId("user-1").email("[email protected]").firstName("Alice").build(), CreateSubscriberRequestDto.builder().subscriberId("user-2").email("[email protected]").firstName("Bob").build())) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v2/subscribers/bulk' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "subscribers": [ { "subscriberId": "user-1", "email": "[email protected]", "firstName": "Alice" }, { "subscriberId": "user-2", "email": "[email protected]", "firstName": "Bob" } ] }' ``` </Tab> </Tabs>

Topics are named groups of subscribers for group-based notification targeting:

<Tabs> <Tab title="Node.js"> ```typescript await novu.topics.create({ key: "engineering-team", name: "Engineering Team" });

await novu.topics.subscriptions.create( { subscriptions: ["user-1", "user-2", "user-3"] }, "engineering-team" );

</Tab>
<Tab title="Python">
```python
novu.topics.create(create_topic_request_dto={"key": "engineering-team", "name": "Engineering Team"})

novu.topics.subscriptions.create(
    topic_key="engineering-team",
    create_topic_subscriptions_request_dto={"subscriptions": ["user-1", "user-2", "user-3"]},
)
</Tab> <Tab title="Go"> ```go _, err := s.Topics.Create(ctx, components.CreateTopicRequestDto{ Key: "engineering-team", Name: "Engineering Team", }, nil)

_, err = s.Topics.Subscriptions.Create(ctx, "engineering-team", components.CreateTopicSubscriptionsRequestDto{ Subscriptions: []string{"user-1", "user-2", "user-3"}, }, nil)

</Tab>
<Tab title="PHP">
```php
$sdk->topics->create(createTopicRequestDto: new Components\CreateTopicRequestDto(
    key: 'engineering-team',
    name: 'Engineering Team',
));

$sdk->topics->subscriptions->create(
    topicKey: 'engineering-team',
    createTopicSubscriptionsRequestDto: new Components\CreateTopicSubscriptionsRequestDto(
        subscriptions: ['user-1', 'user-2', 'user-3'],
    ),
);
</Tab> <Tab title=".NET"> ```csharp await sdk.Topics.CreateAsync(new CreateTopicRequestDto { Key = "engineering-team", Name = "Engineering Team", });

await sdk.Topics.Subscriptions.CreateAsync("engineering-team", new CreateTopicSubscriptionsRequestDto { Subscriptions = new List<string> { "user-1", "user-2", "user-3" }, });

</Tab>
<Tab title="Java">
```java
novu.topics().create().body(CreateTopicRequestDto.builder()
    .key("engineering-team").name("Engineering Team").build()).call();

novu.topics().subscriptions().create("engineering-team")
    .body(CreateTopicSubscriptionsRequestDto.builder()
        .subscriptions(List.of("user-1", "user-2", "user-3")).build()).call();
</Tab> <Tab title="cURL"> ```bash curl -X POST 'https://api.novu.co/v2/topics' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "key": "engineering-team", "name": "Engineering Team" }'

curl -X POST 'https://api.novu.co/v2/topics/engineering-team/subscriptions'
-H 'Content-Type: application/json'
-H 'Authorization: ApiKey <NOVU_SECRET_KEY>'
-d '{ "subscriptions": ["user-1", "user-2", "user-3"] }'

</Tab>
</Tabs>

---

### Inbox Integration

Add an in-app Inbox to your web application. The Inbox provides a bell icon, notification feed, read/archive management, and real-time WebSocket updates.

<Tabs>
<Tab title="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
    />
  );
}
</Tab> <Tab title="Next.js">
bash
npm install @novu/nextjs
tsx
// components/NotificationInbox.tsx
"use client"; // Required for App Router

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

export function NotificationInbox() {
  return (
    <Inbox
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId="subscriber-123"
      subscriberHash="HMAC_HASH"
    />
  );
}
</Tab> <Tab title="Vanilla JS">
bash
npm install @novu/js
typescript
import { Novu } from "@novu/js";

const novu = new Novu({
  applicationIdentifier: "YOUR_NOVU_APP_ID",
  subscriberId: "subscriber-123",
});
</Tab> </Tabs>

HMAC Authentication is required in production to prevent subscriber impersonation. Generate the hash server-side:

typescript
import { createHmac } from "crypto";

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

Manage Preferences

Novu uses a two-level preference system: workflow defaults and subscriber overrides (set by end users).

Workflow-level defaults: Workflow level defaults can be configured in the Novu Dashboard. To do so, click on the workflow you want to configure and then click on the Manage Preferences option.

Subscriber-level overrides:

Workflow-level preferences

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.preferences.update( { workflowId: "weekly-newsletter", channels: { email: false, inApp: true }, }, "subscriber-123" ); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.preferences.update( subscriber_id="subscriber-123", patch_subscriber_preferences_dto={ "workflow_id": "weekly-newsletter", "channels": {"email": False, "in_app": True}, }, ) ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.Preferences.Update(ctx, "subscriber-123", components.PatchSubscriberPreferencesDto{ WorkflowID: novu.String("weekly-newsletter"), Channels: &components.Channels{Email: novu.Bool(false), InApp: novu.Bool(true)}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->preferences->update( subscriberId: 'subscriber-123', patchSubscriberPreferencesDto: new Components\PatchSubscriberPreferencesDto( workflowId: 'weekly-newsletter', channels: new Components\Channels(email: false, inApp: true), ), ); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.Preferences.UpdateAsync("subscriber-123", new PatchSubscriberPreferencesDto { WorkflowId = "weekly-newsletter", Channels = new Channels { Email = false, InApp = true }, }); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().preferences().update("subscriber-123") .body(PatchSubscriberPreferencesDto.builder() .workflowId("weekly-newsletter") .channels(Channels.builder().email(false).inApp(true).build()) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X PATCH 'https://api.novu.co/v2/subscribers/subscriber-123/preferences' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "workflowId": "weekly-newsletter", "channels": { "email": false, "inApp": true } }' ``` </Tab> </Tabs>

Global preferences

<Tabs> <Tab title="Node.js"> ```typescript await novu.subscribers.preferences.update( { channels: { email: false, inApp: true }, }, "subscriber-123" ); ``` </Tab> <Tab title="Python"> ```python novu.subscribers.preferences.update( subscriber_id="subscriber-123", patch_subscriber_preferences_dto={ "channels": {"email": False, "in_app": True}, }, ) ``` </Tab> <Tab title="Go"> ```go _, err := s.Subscribers.Preferences.Update(ctx, "subscriber-123", components.PatchSubscriberPreferencesDto{ Channels: &components.Channels{Email: novu.Bool(false), InApp: novu.Bool(true)}, }, nil) ``` </Tab> <Tab title="PHP"> ```php $sdk->subscribers->preferences->update( subscriberId: 'subscriber-123', patchSubscriberPreferencesDto: new Components\PatchSubscriberPreferencesDto( channels: new Components\Channels(email: false, inApp: true), ), ); ``` </Tab> <Tab title=".NET"> ```csharp await sdk.Subscribers.Preferences.UpdateAsync("subscriber-123", new PatchSubscriberPreferencesDto { Channels = new Channels { Email = false, InApp = true }, }); ``` </Tab> <Tab title="Java"> ```java novu.subscribers().preferences().update("subscriber-123") .body(PatchSubscriberPreferencesDto.builder() .channels(Channels.builder().email(false).inApp(true).build()) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X PATCH 'https://api.novu.co/v2/subscribers/subscriber-123/preferences' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "channels": { "email": false, "inApp": true } }' ``` </Tab> </Tabs>

Preference resolution order (most specific wins):

  1. Subscriber workflow preference
  2. Subscriber global preference
  3. Workflow default
  4. System default (all channels enabled)

The Inbox component includes a built-in Preferences panel accessible via the settings icon, or use <Preferences /> as a standalone component.

For additional help, check the Novu documentation or contact us at [email protected].