TL;DR
Components are the new API. If you’re a modern stack web developer—meaning you’re working in the JavaScript ecosystem, and either use components, or are creating your own. This is about the latter—how we re-imagined our own <Inbox />
component, and built an all-new set of components. Our new set now is a smaller bundle size, has unparalleled customization and composability, and comprehensive localization support—all designed with developers in mind. Introducing two new SDKs, @novu/js
and @novu/react
.
Table of Contents
Introduction and overview
The component is known by many names: Notification Popover, Notification Feed, Notification Center, Activity Feed, Updates Feed, Alert Box, Event Feed, and more.
Its name and function vary depending on the specific use case of the app. At its core, the Inbox component serves as a crucial communication channel between the app and its users.
To make sure we are on the same page, here are few examples:
Linear Inbox
It’s where you receive an Inbox notification for key events on your subscribed issues. You're automatically subscribed to issues when you create them, are assigned them, or are mentioned in an issue description or comment.
- (From Linear Docs)
Notion Inbox & Page Notifications
The inbox and notifications in Notion help you stay on top of work that needs your attention, and changes made to the pages and projects you care about. The more people you collaborate with in your workspace, the more helpful these features become.
- (From Notion's Help Center)
Slack Activity tab
Use the Activity tab on desktop or mobile to quickly review all of your notifications.
Think of it as a place to focus your attention or to get caught up after some time away from Slack. Click or tap the Activity icon on desktop or mobile to open a chronological feed of your notifications. Select a filter to switch between the different types of notifications, like mentions, replies to threads, or reactions to your messages.
- (From Slack's Help Center)
As you can already understand, the scope of an Inbox component could be very narrow or extremely robust, all comes to the app’s use case.
Building real-time event-based updates, messages, and notifications for your app can be highly challenging, especially when factoring in ongoing maintenance and future feature enhancements.
To simplify this process, we’ve developed an open-source, ready-to-use React component that provides an out-of-the-box solution for adding in-app notification capabilities, giving you a head start on your development.
Ready-To-Use Inbox
Here is how the code looks like
import { Inbox } from '@novu/react';
export const novuConfig = {
applicationIdentifier: 'YOUR_APPLICATION_IDENTIFIER',
subscriberId: 'YOUR_INTERNAL_SUBSCRIBER_ID',
};
export function Inbox() {
return (
<Inbox {...novuConfig} />
);
}
In this post, we share the “behind-the-scenes” process, providing an in-depth look at WHY and HOW we developed our new components, given that we already had functional ones. I hope you’re able to learn how you can build your own components.
If you prefer to dive right into download and test, jump to this section.
Development rationale
We broke the 1st rule of Programming, “If it works.... don't touch it!”
In May 2022, Novu released the first version of @novu/notification-center
; our main repository had around 2K GitHub stars and barely a team of six people; it was a huge milestone for us!
Since then, we've launched numerous features designed to simplify developers' work.
As we build new technology and gain broader exposure, we accumulated valuable insights, feedback, and user requests. These drive us toward innovation, often resulting in the accumulation of technical debt and necessary refactoring.
Breaking down v1 of Novu’s Ul Inbox component collection
React component
The first comprehensive In-App component (@novu/notification-center
) was a React library because that is what we knew best.
import {
NovuProvider,
PopoverNotificationCenter,
NotificationBell,
IMessage,
} from "@novu/notification-center";
function Novu() {
return (
<NovuProvider
subscriberId={"SUBSCRIBER_ID"}
applicationIdentifier={"APPLICATION_IDENTIFIER"}
>
<PopoverNotificationCenter colorScheme="dark">
{({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />}
</PopoverNotificationCenter>
</NovuProvider>
);
}
The main Notification Center React component had 4 pieces:
-
NovuProvider
- This is a wrapper component that provides the necessary context for Novu's notification center to function. It should be placed at the top level of your application hierarchy. -
PopoverNotificationCenter
- This is a pre-built component that displays notifications in a popover format. -
NotificationBell
- This component renders the notification bell icon. It's typically used within thePopoverNotificationCenter
and can display the number of unseen notifications. -
IMessage
- This is an interface type representing a notification message. It contains information about the notification, including its content, status, and any associated actions.
Web component
Novu wanted to cater to all its community members and users. So… The next package wasn’t actually a package; it was another component within @novu/notification-center
. We called it “Web Component*”.*
import { NotificationCenterWebComponent } from "@novu/notification-center";
The Notification Center Web Component was a custom element that can be used in any web application. It was built by wrapping the React components and consists from its core the three pieces: NovuProvider
, PopoverNotificationCenter
, and NotificationBell
combined together into one. Our first bundle!
Vue component
The @novu/notification-center-vue
package provided a Vue component wrapper over the Notification Center Web Component that you can use to integrate the notification center into your Vue application. The main issue is that it’s using React along with ALL THE DEPENDENCIES under the hood.
Angular component
The @novu/notification-center-angular
package provided an
Angular component wrapper over the Notification Center Web Component
that you can use to integrate the notification center into your Angular
application.
iframe embed
If you were using an unsupported client framework, you could use our embedded script. This generated the notification center inside an iframe.
With every new wrapper, time, frequent questions, requests, and issues started to pile up, like:
- Very big bundle size
- Limitations and general friction in customization and styling.
- Many warnings during webpack compile process
- “Delete All Notifications” option
- Marking a message as "seen" briefly reverts to "unread" during data updates
And more.
We made bug fixes and feature enhancements and even released the @novu/headless
, offering a package with just the essential API methods so that users could easily incorporate Novu’s notification system into any framework or vanilla JavaScript project without being constrained by our default UI or dependencies.
With the team expansion, new people started to bring new questions and perspectives to the table.
Development process for the Inbox Component
With the launch of our code (and developer) first code-based @novu/framework
workflow approach, we set out to re-evaluate other areas we could make developer’s lives easier. We already believed that components were the answer to drastically reduce the time and complexity required to provide end users a powerful in-app experience. Brainstorming ways we could improve the experience became the next step.
Our straightforward guiding principles
- How can we help make it easier for our users to implement an in-app notifications component?
- How might we ensure that our users have enough flexibility to deliver the experience they need to provide to their end users?
- What can we do to make the developer a hero inside their business?
Brainstorming and feature selection
We started with a brainstorming session to list down the features we wanted in the new Inbox component. After the session, we selected the features to tackle in the first release.
Feature selection
- First release: Features for the first release were chosen based on team discussions and planning sessions.
- Future versions: Features for future versions are discussed in ongoing planning meetings and are captured on the Linear board and the roadmap.
Design phase
Nikolay, our Product Designer took the requirements and created initial inbox component designs.
Planning and breaking down designs
The team (in this case, Pawel and Biswa) started breaking down the design into smaller pieces to create tickets on Linear.
They conducted several POCs and planning sessions to decide on the packages to create and the framework to use for the UI.
Initial planning
-
Packages to create
-
@novu/js
: The foundational package that will be used by other Novu packages such as@novu/react
and@novu/vue
(in the future).This package includes everything (headless and UI components).
@novu/react
: Chosen due to React's popularity and significant customer base.
-
Package development
@novu/js
-
Headless: API client and Novu classes for custom UIs, replacing the old
@novu/headless
due to its complexity and unclear documentation. -
UI Components: Exporting UI components designed in Figma:
- Inbox: Default all-in-one component.
- Bell: Includes unread count and customizable bell icon.
- Preferences: Full-page list for user settings.
- Notifications: Full-page notifications similar to Twitter's notification feed.
@novu/react
- Consumes UI from
@novu/js
and renders it in React. Each component can be fully replaced by the user’s custom components, offering flexibility and customization.
UI framework research
The team then researched various UI frameworks/libraries, including
- SolidJS
- React
- Vue
- Lit
- Svelte
They created some POCs with these frameworks and decided on SolidJS due to its lightweight nature, superior performance compared to React, and similar syntax to React, which facilitated ease of adoption.
CSS solutions research
The team researched CSS solutions and customization levels while considering naming conventions for the headless part. Biswa was responsible for researching CSS solutions, and Pawel worked on headless naming conventions.
CSS solutions candidates
- vanilla-extract
- Panda CSS (Not chosen due to suboptimal experience)
- Stitches
- Styled-jsx
- Linaria
- Tailwind
- Plain CSS
We chose Tailwind because of compilation to pure CSS classes and better DX.
We aim to provide developers with multiple ways to customize the Novu Inbox component styling.
The available customization options include
- Style objects
- Classnames
- CSS variables
- Target classnames
API endpoints
We introduced a new /inbox
endpoint path instead of using the existing notification center API due to various changes in the Notification DSL and the need for more straightforward, leaner endpoints.
Our docs won’t publicly expose these endpoints. (We might expose the new endpoints in the future if our users share their needs and use cases). Instead, we encourage our users to use our Component ****SDK and higher-level library UI packages.
A ‹Component/›
is worth a thousand APIs
With amazing notification UI components as our primary goal, we realized that Novu wouldn't be a traditional API company. We focused on building a fantastic frontend component SDK, rather than a traditional backend REST SDK.
Components are a higher level of abstraction than REST SDKs. They provide UIs to end-users instead of just functions to developers, so they inherently offer more value.
Localization
Our team mate George worked on Localization. Novu needs to offer a way to override the default copy and localize the content of the <Inbox/>
component.
The most intuitive way to do this is via a localization
prop.
import { Inbox } from '@novu/react';
function Novu() {
return (
<Inbox
options={{
subscriberId: 'SUBSCRIBER_ID',
applicationIdentifier: 'APPLICATION_IDENTIFIER',
}}
localization={{
'inbox.status.archived': 'Archived',
'inbox.status.unread': 'Unread',
'inbox.status.options.archived': 'Archived',
'inbox.status.options.unread': 'Unread',
'inbox.status.options.unreadRead': 'Unread/Read',
'inbox.status.unreadRead': 'Unread/Read',
'inbox.title': 'Inbox',
'notifications.emptyNotice': 'No notifications',
locale: 'en-US',
}}
/>
);
}
The localization
object should remain flat for simplicity and maintainability.
flat
<Inbox localization = {{
'notification.primaryButtonText': 'Accept',
}}
We might provide localization templates for all languages via a new localization package, or provide a locale prop and localize automatically (could auto-detect too).
Notification schema improvement
We planned to improve the Notification Schema for a better Developer Experience. Biswa created a DX guide for the updated schema and coordinated with Richard from the framework
team to ensure alignment.
// ---- EXISTING ENUMS ----
enum ChannelTypeEnum {
IN_APP = 'in_app',
EMAIL = 'email',
SMS = 'sms',
CHAT = 'chat',
PUSH = 'push',
}
// existing enum
enum ButtonTypeEnum {
PRIMARY = 'primary',
SECONDARY = 'secondary',
}
// ------------------------------
// ======== NEW TYPES =======
type Action = {
label: string; // Human-readable label for the action.
isCompleted: boolean; // Indicates whether the action is completed (done/pending)
};
type Subscriber = {
id: string;
firstName?: string;
lastName?: string;
avatar?: string;
subscriberId: string;
};
type Redirect = {
url: string;
// maybe in V2
// REF: https://developer.mozilla.org/en-US/docs/Web/API/Window/open#target
target: '_self' | '_blank' | '_parent' | '_top' | '_unfencedTop';
}
type InboxNotification = {
id: string; // Unique identifier for the notification.
subject?: string; // Subject/Title of the notification, optional for backward compatibility
body: string; // Content/body of the notification.
to: Subscriber; // Recipients of the notification.
read?: boolean;
archived?: boolean;
createdAt: string; // Timestamp indicating when the notification was created.
readAt?: string; // Timestamp indicating when the notification was read, optional.
archivedAt?: string; // Timestamp indicating when the notification was archived, optional.
avatar?: string; // The icon URL
primaryAction?: Action;
secondaryAction?: Action;
channelType: ChannelTypeEnum; // Type of channel through which the notification is sent.
tags?: string[]; // Array of tags that were applied on the workflow
redirect?: Redirect; // URL for performing the redirect on notification click, optional.
}
Notification schema
Defines properties of notification (e.g., subject, content, avatar, read status, archived status) and how it is represented in code and the database. This schema change was also a factor in opting for new endpoints.
Comparison
@novu/notification-center vs. @novu/react
This comparison highlights the differences between the old @novu/notification-center
library and the new @novu/react
library, emphasizing the improvements and benefits introduced in the new version.
Feature | @novu/notification-center | @novu/react |
---|---|---|
Component structure | Multiple components (NovuProvider, PopoverNotificationCenter, NotificationBell) | Single All-in-One, and separate dedicated components. |
Installation | npm install @novu/notification-center | npm install @novu/react |
Setup complexity | Moderate complexity with multiple components | Simplified setup with a single component |
Customization | Limited options | Extensive customization options (backend URL, socket URL, state management, localization) |
Security | Basic security, requires manual setup for HMAC encryption | Enhanced security with built-in HMAC encryption support |
State management | No direct state management for popover | Direct state management via open prop |
Localization | Not supported | Supported via localization prop |
Real-time updates | Uses useSocket hook for real-time updates | Establishes the single active WebSocket connection that is shared between multiple tabs. |
Notification actions | Separate handlers for onNotificationClick and onActionClick | Streamlined handling within the Inbox component |
API design | More complex and less intuitive | Simplified and more intuitive |
Open source | Yes! | Yes, and now more extensible and community-building. |
Wrapping up
Because our goal is to help Novu users implement an in-app inbox, ensure that they have enough flexibility, and make them heroes at their company, we are doubling down on providing the new gold standard in developer tools: UI notification components, starting from our new Inbox component.
And even if frontend developers don't like our components for any reason, they can still build their own UIs without involving a backend developer and a backend SDK.
Once again. we invite you to explore the new @novu/js
and @novu/react
packages. These tools are designed to make your development process smoother and more efficient.
Novu's JavaScript SDK
Installation
Install @novu/js
npm package in your app
npm install @novu/js
Getting started
Add the code below to your application
import { Novu } from '@novu/js';
const novu = new Novu({
applicationIdentifier: 'YOUR_NOVU_APPLICATION_IDENTIFIER',
subscriberId: 'YOUR_INTERNAL_SUBSCRIBER_ID',
});
const { data: notifications, error } = await novu.notifications.list();
Novu's React SDK
Installation
Install @novu/react
npm package in your app
npm install @novu/react
Getting started
Add the below code in the app.tsx
file
import { Inbox } from '@novu/react';
function Novu() {
return (
<Inbox
options={{
subscriberId: 'SUBSCRIBER_ID',
applicationIdentifier: 'APPLICATION_IDENTIFIER',
}}
/>
);
}
Learn more in our changelog and docs
Liked what you read? Hit follow for more updates and drop a comment below. I’d ❤️ to hear your 💭
Top comments (1)
Notified 😄