Changelog

Product updates, improvements, and fixes

Follow us on X

All changelog posts

  • Multi-Tenancy

    Novu now supports full multi-tenancy using Contexts, making it possible to isolate notifications, preferences, and credentials across tenants, workspaces, or applications within a single project.

    Author:Adam Chmara
    Adam Chmara

    You can now scope any notification to a specific tenant or workspace by attaching a Context when triggering workflows or configuring integrations.

    A Context acts as a persistent identity key for a logical boundary in your system:

    {
      "type": "tenant",
      "id": "org-acme",
      "data": { "name": "Acme Corp", "region": "us-east-1" }
    }

    All notifications, and credentials associated with that workflow run will inherit this Context automatically.

    How to use it

    When triggering workflows:

    await novu.trigger('payment-success', {
      to: { subscriberId: 'user-123' },
      payload: { amount: '$250' },
      context: { type: 'tenant', id: 'org-acme', data: { name: 'Acme Corp.', logo: 'http://logo.com' } },
    });
    • All generated messages are tagged with the tenant:org-acme key.
    • You can later query or filter by this key using the API.
    GET /v2/notifications?contextKey=tenant:org-acme

    Within templates or layouts, context.tenant.data can be used for branding or logic:

    {{context.tenant.data.name}} has a new billing update.

    Supported use cases

    • Per-tenant isolation for notifications
    • Tenant-level credentials for chat integrations (e.g., Teams, Slack)
    • Dynamic content and branding driven by context data
    • Workspace or app-level scoping within shared environments

    Real-world examples

    • A SaaS platform with multiple customer organizations sending workspace-specific updates.
    • An enterprise product using chat integrations where each workspace manages its own credentials.
    • An application separating preferences across multiple apps or environments under one Novu project.

    Multi-tenancy in Novu introduces a consistent and reliable way to manage notification boundaries across tenants, workspaces, or applications, without duplicating workflows or environments.


    Contexts make it simple to design systems where every notification and credential is tied to the right scope, predictable, queryable, and isolated by design.

    Learn more

  • Contexts

    Define tenant, app, or workspace context once, and Novu automatically scopes every notification, Inbox feed, preference, and delivery path to that context. No duplicated workflows, no custom filters, and no risk of cross-tenant leaks.

    Author:Adam Chmara
    Adam Chmara
    Cover
    Early Access

    This feature is currently in beta. If you'd like to access Contexts, please email our support team at [email protected]

    A Context in Novu is a structured object that describes the environment or scope to which a notification belongs. Instead of embedding metadata (like tenant ID or app name) into every trigger payload, you can now store it once and reference it everywhere.

    PropertyDescriptionExample
    typeCategory key (e.g. tenant, app, region)"tenant"
    idUnique identifier within that type"org-acme"
    dataJSON object with custom metadata{ "name": "Acme Corp", "plan": "enterprise" }
    keyUnique composite identifier"tenant:org-acme"
    Contexts management window within Novu Dashboard

    Context object schema

    When defining contexts, Novu supports multiple formats per key-value pair, allowing you to store and reference metadata relevant to your workflows and templates.

    await novu.trigger('welcome-email', {
      to: 'user123',
      payload: { userName: 'John' },
      context: {
        // Simple string format
        tenant: 'acme-corp', 
        
        // Rich object format (ID only)
        app: { id: 'jira' },                    
        
        // Rich object format (ID + data)
        region: {                               
          id: 'us-east',
          data: {
            name: 'US East',
            timezone: 'America/New_York',
            currency: 'USD'
          }
        }
      }
    });

    How it works

    1. Create reusable contexts

    POST /v2/contexts
    
    {
      "type": "tenant",
      "id": "org-acme",
      "data": { "name": "Acme Corp", "plan": "enterprise" }
    }

    Contexts are stored with unique environment and organization indexes, ensuring clean separation across projects.

    2. Reference them in workflow triggers

    await novu.trigger('payment-success', {
      to: 'user-123',
      payload: { amount: '$250' },
      context: {
        tenant: 'org-acme',
        app: { id: 'jira', data: { name: 'Jira Integration' } },
      },
    });
    • Contexts can be passed as simple strings or rich objects.
    • Missing contexts are created automatically.
    • All related notifications, messages, and jobs store the context keys for traceability.

    3. Resolved automatically at runtime

    During message delivery, the worker reconstructs the context from its stored key(s), exposing all context data in your templates:

    {{context.tenant.data.name}}   →   Acme Corp
    {{context.app.id}}   →.  jira

    4. Manage through API or Dashboard

    • GET /v2/contexts/:type/:id — retrieve
    • PATCH /v2/contexts/:type/:id — update data
    • GET /v2/contexts — list & search
    • DELETE /v2/contexts/:type/:id — remove

    Visit the documentation to learn how to create, update, and retrieve a Context.

    <Inbox /> with Contexts

    Note

    Requires <Inbox /> version 3.11.0 or higher

    As products mature, they typically shift from serving a single user group to supporting multiple groups.

    Each organization, environment, or workspace has its own unique data, brand, and set of rules.

    Most notification systems regard users as a single group with the same feeds, which leads teams to create weak filters and have duplicate subscribers across different tenants.

    This is where <Inbox /> with Contexts excels for managing multi-tenancy. It offers built-in support directly at the notification layer, providing each tenant with a dedicated Inbox setup. This approach keeps your subscriber model and workflows intact without requiring any duplication.

    Inbox Scoped by Context

    When you render the Inbox, merely pass the same context:

    The <Inbox /> knows precisely which messages to load, filtered by context.

    Subscribers can switch between tenants or workspaces. Each one shows its own separate feed and settings.

    • Inbox A → tenant:org-acme
    • Inbox B → tenant:org-globex
    • Inbox C → tenant:org-novu

    Each has its own unread counts, archived messages, and rules. They all use a single subscriber ID (user-123).

    <Inbox  
      applicationIdentifier="YOUR_APP_ID"  
      subscriberId="YOUR_SUBSCRIBER_ID"  
      context={{ tenant: 'acme', app: 'dashboard' }}  
      contextHash="CONTEXT_HASH_VALUE"  
    />

    Learn how to use contexts in the Inbox component to filter and personalize notifications for your subscribers

    Improvements (2)
    • feat(dashboard,api-service, worker): include inbox unread count in the FCM and Expo messages available on the app badge
    • feat(worker): device token invalidation logic for FCM and Expo is now enabled, in active tokens will be removed and notified via the message.failed webhook
    Fixes (4)
    • fix(api-service,application-generic): Passing null values when updating subscriber will now properly recognize and persist them
    • fix(api-service): translation keys escaping bug caused \"\ fields when used escaped fields in the i18n files
    • fix(js): notification count display for 99+ was properly fixed, where previously 100 was shown.
    New Providers (1)

  • Dynamic Delay

    Dynamic Delay (Payload-Driven Scheduling) lets you control when each workflow continues by passing a datetime field in your payload. Novu pauses and resumes execution at that moment, giving you precise, per-trigger delivery without fixed delays or cron rules.

    Author:Dima Grossman
    Dima Grossman
    Cover

    Schedule a workflow to pause until a datetime you provide at trigger time. Configure a Delay step with a delayPath, then pass the ISO datetime in your event payload. Each trigger can resume at its own time, making it ideal for appointment reminders, renewals, or any other per-user timing.

    What you can do

    • Per-trigger timing: Each event determines its own resume time via its payload.
    • Works anywhere in the workflow: Before or between any steps (including before digests).
    • Cancelable: Use transactionId to cancel pending delays.
    • Frictionless: No cron required for per-user scheduling.

    How it works

    In the Dashboard

    1. Add a Delay step
    2. Select Dynamic
    3. Set Path for scheduled date (e.g., payload.sendAt)

    At trigger time (API/SDK)

    Send an ISO 8601 UTC datetime on that path:

    {
      "workflowId": "appointment-reminder",
      "to": { "subscriberId": "user_123" },
      "payload": {
        "sendAt": "2025-12-01T09:00:00.000Z",
        "name": "Alex"
      }
    }

    The workflow pauses and resumes exactly at payload.sendAt.

    Behavior & validation

    • Required: delayPath on the step AND an ISO 8601 UTC value (...Z) in the payload.
    • If missing/invalid: The Delay job is canceled, and execution details show DELAY_MISCONFIGURATION with an error message.
    • If the datetime is in the past, it is rejected with a “future date required” error.
    • Canceling: client.cancel(transactionId) stops pending delay jobs for that transaction.

    Example Use Cases

    Appointment reminder

    • Delay (Scheduled) → delayPath = sendAt
    • After the delay → SMS/Email/In-app step
    • Trigger with sendAt per appointment.

    Subscription renewal

    • Delay (Scheduled) → delayPath = renewalAt
    • After the delay → Email/In-app notice
    • Trigger with renewalAt per subscriber.
  • Throttle Step

    The new Throttle Step allows you to limit the frequency of a workflow execution for each subscriber within a specified time window. This will prevent duplicate or excessive notifications across all channels. You can use fixed or dynamic windows. Perfect for managing high-frequency alerts, cron jobs, or multi-project notifications.

    Author:Dima Grossman
    Dima Grossman
    Cover

    The new Throttle step in the Novu workflow editor allows you to limit the number of notifications a subscriber receives within a specified time frame, ensuring they receive the right amount, neither too many nor too few.

    When a trigger fires repeatedly (e.g., from cron jobs or high-frequency alerts), throttling ensures that subscribers don’t receive duplicate messages across any channel.

    Throttle Step

    You can now:

    • Configure throttling directly in the workflow editor.
    • Define Fixed or Dynamic throttle windows.
    • Limit executions by subscriberId or add a secondary grouping key from your payload (e.g., payload.projectId).
    • Prevent notifications from executing beyond your defined threshold (even for critical workflows).
    • Transparently control frequency across all channels (email, in-app, SMS, chat, push).

    Configuration options:

    • Fixed window: Set a predefined duration (e.g., “1 per hour”) with an execution threshold.
    • Dynamic window: Define flexible time ranges using trigger payloads (payload.throttleUntil or payload.customWindow).
    • Group throttling by: Combine subscriber and payload-level keys for granular control (e.g., per project or account).

    Learn more by visiting the Throttle Step documentation →

    Improvements (3)
    • Schemas now support null values for optional fields in workflows, ensuring executions no longer fail when data is intentionally unset.

    • Add support for excluding specific subscribers from topic-triggered workflow fanouts using an optional exclude array.

    • Add support for defining a custom display order of workflows in the Preferences UI, allowing prioritized workflows to appear at the top.

    Fixes (1)
    • fix(dashboard-ui): Unify and align all dashboard tables (Workflows, Subscribers, Topics, Topic Subscriptions) using the new OriginUI table component for consistent layout, pagination, and record visibility.

  • Translations in Email Layouts

    Translations support in email layouts. Localize headers, footers, and templates once - and reuse them across all your workflows.

    Author:Paweł Tymczuk
    Paweł Tymczuk

    You can now add translations directly at the email layout level. This means that when you configure a default or reusable email layout in your organization environment, you can also provide localized versions of that layout.

    Previously, translations were only available on a per-email-step basis within workflows. Now, layouts themselves can be localized once, and the translations will automatically apply to any email workflow that uses that layout.

    How It Works

    1. Go to Workspace → Layouts and create or edit a layout.
    2. Add translations for your chosen locales (JSON format).
    3. Any workflow email step using that layout will render the correct translation automatically, based on the subscriber’s locale.

    Layout and step content are translated independently, then combined during rendering to deliver fully localized emails.

  • Scheduled Delay

    Schedule your workflows with precision - pause until the exact minute, hour, or weekday in each subscriber’s local time, and even align delivery with their working hours.

    Author:Paweł Tymczuk
    Paweł Tymczuk
    Cover

    You can now schedule workflow pauses using an intuitive time selector - allowing you to delay messages until an exact time, day, or week in your subscriber’s local timezone.

    Scheduled delay

    This new Scheduled Delay option (when configuring the Delay step) provides developers with fine-grained, time-aware control over workflow timing, allowing you to match your subscribers’ schedules, respect working hours, and ensure messages arrive at the optimal moment, not just after a fixed interval.

    What you get:

    • Two delay modes: choose between
      • Regular Delay: wait a fixed duration (e.g. “5 minutes”)
      • Scheduled Delay: wait until a specific time or date pattern
    • Flexible scheduling options:
      • Until minute — delay until the next minute mark
      • Until hour — at X minute(s) of the hour
      • Until day — at X hour(s) : X minute(s)
      • Until week — choose one or more days (Mon–Sun) at X hour(s) : X minute(s)
      • Until month — choose specific day(s) of the month and time of day
    • Time zone–aware delivery: All delays are evaluated and executed in the subscriber’s local time zone.
    • Working hours support: toggle “Extend to subscriber’s schedule” to automatically hold delivery until the subscriber’s next available time window.
    • Configurable limits: by default, delays can extend up to 1 day (contact support to extend for longer intervals)

    Example use cases:

    • Send a weekly digest every Friday at 9:00 AM subscriber local time.
    • Deliver a trial reminder at 8:00 AM on the day of expiry.
    • Respect user schedules by delaying notifications until working hours.

    How to use it:

    In the workflow editor → add a Delay step → select “Scheduled” → configure your timing (minute, hour, day, week, or month) and enable “Extend to subscriber’s schedule” if needed.

Free to start, ready to scale

10K events/month free forever. From weekend projects to enterprise scale, we've got you covered.