If you opened this blog post, you’re probably about to wade into the complicated ecosystem of notification and customer engagement tooling. It can feel like a daunting task. Not to fear, in this post we’re here to walk you through the basics of notification systems and the ecosystem of tools, frameworks, and vendors that surround them.

In this post we’ll cover:

  • The key components of a notification system and their relevant use cases
  • An overview of the tools, frameworks, and services available when building a notification system
  • How to put these together to make the right choice for your use case and product

Let’s get started.

The key components of a notification system

Before you start to look at the tools available to help in building a notification system, it’s helpful to understand the different components that comprise these systems and their role within them.

Channels and providers

A channel defines the destination of the notification. Email, SMS, push, and in-app feeds are all different types of channels. For many channel types you’ll use a provider to help manage last-mile delivery of the notification to the recipient, for others you may deliver directly to the native platform that the channel represents (e.g. Slack, APNS) or to a channel you manage in-house (e.g. a custom built in-app feed.)

Template management

Notifications are backed by a template that determines the content of the notification on a particular channel. Some channels, like email, have more complex templating needs, given they need to produce HTML that’s valid on email clients. (Hello <table>, my old friend.)

<table
  class="body-action"
  align="center"
  width="100%"
  cellpadding="0"
  cellspacing="0"
>
  <tr>
    <td align="left">
      <table width="100%" border="0" cellspacing="0" cellpadding="0">
        <tr>
          <td align="left">
            <table border="0" cellspacing="0" cellpadding="0">
              <tr>
                <td>
                  <a href="{{ confirm_url }}" target="_blank"
                    >Log in to Knock</a
                  >
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>
The HTML/CSS to render a button in an email.

Notification templates usually need a templating language that can handle control flow and loops, such as liquid, handlebars, or mustache. Often templates will live in the backend codebase and therefore need to be created and maintained by engineers.

Orchestration engine

The orchestration engine determines which notifications to send to which recipients on which channels. In products with a single channel (like email) or simple use cases, orchestration might be coupled with application logic (e.g. when an order is placed, send an email). In more complex products with multi-channel notification systems, the orchestration engine often evolves into its own component with complex business rules.

Preference management UI and data model

For products with few notifications, this can be a set of simple checkboxes but as the number of notifications increases, typically so does the complexity of the preferences that power those notifications. Doing so involves a backend model to store notification preferences as well as a component to present the preferences model to users in the frontend.

Notification data normalization

Once a product starts sending notifications, you’ll want a view into how users interact with your product’s messaging. On an individual-channel basis, notification analytics data for some channel types will be available directly within providers (e.g. the SendGrid dashboard provides a view into opens, link clicks, etc.) while other channels (such as push or in-app) will need custom application handling code to capture engagement data for the notifications delivered.

Once you start evaluating cross-channel notification data, it’s important to develop a standard set of statuses you can use to normalize per-provider/channel data and colocate it with the rest of your product usage data to understand how notification efficacy drives user retention and other business objectives.

Reliability: delivery job queues, fallback provider logic

The ability to ensure notification delivery to downstream providers is a critical component of a notification system. While simple systems often have notification delivery coupled with application code, more complex systems employ some sort of job queue to offer more guarantees around delivery and ensure at-most once delivery of notifications.

As with any third-party integration, it’s important to consider edge cases in your delivery queue such as provider rate limits, network failures, and service outages. For mission-critical notifications, teams will often consider fallback providers to ensure notification delivery in the event of a provider outage (or to load balance across providers to avoid hitting rate limits.)

Observability tooling

Since notifications are business critical systems, a clear understanding of how your notification system is performing is key to proactively identifying and debugging issues. It’s important to see logs about which notifications a user has received and which notifications failed to be delivered. Additionally, you’ll want to consider having telemetry data for the running of the notification system itself. Metrics such as delivery failure rate, send time, and queue depth are important to understand the running operation of the notification system.

In-app notifications

Though in-app notifications are often viewed as just another channel, they have a unique requirement that separates them from other transactional notifications: they must be persisted. To power in-app notifications in your product, you will need three main components:

  • A backend to store and serve the notifications for a given user and to manage the state of each notification (e.g. seen, read, archived)
  • A real-time layer to deliver notifications to users currently active in the product
  • A UI component to render and interact with the notifications (such as a notification feed or inbox)

Token and credential management

Some notification channels (like push) require unique device tokens to be obtained in order to send a notification to the recipient on the device. These push tokens may become invalid or be rotated by the underlying platform owner and as such need to be regularly monitored. Similarly, other credentials, like OAuth or bot tokens, also need to be obtained and securely stored in order to send notifications to chat channels such as Slack or Microsoft Teams.

Batching, digesting, and crons

As you ship more features (and ship more notification types in the process) and general usage on your product increases, decreasing notification volume for users (while maintaining throughput of valuable information) becomes critical to delivering a good notifications experience. There are two approaches teams take to this:

  • Batching: collapse multiple notifications of the same type into a single notification
  • Digesting: deliver a summary of all missed notifications over a window

Depending on which you’re looking to do, you’ll need some combination of a cron engine or a batching window system. For more detail on this specific notification system component, check out our technical deep dive on building a batched notification engine.

An overview of notification system tooling, frameworks, and vendors

Now that we’ve covered the primary components that make up a notification system, let’s get to the tools you can use to help you ship a notification system of your own.

There are a few categories of off-the-shelf options available to you that we’ll cover in this section:

  • Backend notification libraries
  • Push notification delivery services
  • Marketing automation/customer engagement platforms
  • Third-party notification systems

Each has different scenarios in which they’re effective and in which they’re not. Which you decide to use will depend on your use case, your stack, and your priorities in building your notification system.

Backend notification libraries

There are a number of open-source backend libraries you can use to bootstrap the key components of your notifications system.

These libraries aren’t full-fledged notifications systems. With most of them you’ll be left managing preferences, delivery queues, and a number of the other components we mentioned above.

What they are is a quick, language-specific way to start sending cross-channel notifications from your backend codebase, and typically they help to abstract away the tedium of writing provider-specific notifications code. Using a backend library is a step up from building a notification system directly on top of provider SDKs, as it provides you a centralized abstraction in which to create new notifications and extend into different channels or easily swap providers down the road.

Here are a few examples of popular open-source notification libraries:

Pros

  • Backend notification libraries make it easy to get started quickly. If you’re just looking to quickly send a few templates across a number of channels, these libraries give you a good way to manage that without writing provider-specific implementation code.
  • Backend notification libraries enable provider flexibility. Because these libraries act as a point of abstraction above providers, they can be used to easily switch out providers if needed.
  • Backend notification libraries exist as a part of your codebase. This means that you can control all of the logic of how a notification is sent, where it's sent, and who it's sent to. You'll also be able to get as much visibility into the notification system as you would any other part of your service.

Cons

  • Backend notification libraries don’t solve for reliable delivery. While some libraries have mechanisms to integrate with deferred job queues, you'll still need to think about the retry semantics and failure handling for delivering your notifications.
  • Backend notification libraries don’t provide UI/dashboards for managing the system. If you’re a larger organization that needs better visibility into your notification system as a whole, you’ll need to build an internal dashboard for product managers and other stakeholders to see into (and potentially make updates to) the notification system.
  • Backend notification libraries won't help with orchestration logic. If you're a team that is looking to build more complex rules around orchestration of your notification, you'll be left implementing this within your codebase on top of these libraries.

If you’re a small team of mostly engineers looking to ship notifications quickly, it’s worth looking into a backend library to see if it will meet your requirements. If you’re a larger organization with other stakeholders involved in your notification system, or that’s looking for a solution to manage delivery queues and other scaling concerns, you may want to explore other options.

Push notification delivery services

Next is a category of services that you’ll primarily see associated with push notifications. These services solve the problem of managing push notifications with APNs (Apple push notification service) and FCM (Firebase Cloud Messaging) on your behalf. When you’re a mobile-first product that manages applications on both iOS and Android, you need to do things such as manage device tokens, confirm recipient to downstream platforms, and scale in general. These platforms help you do this.

Here are a few examples:

  • Google Firebase Cloud Messaging (FCM) – this can be confusing as it's also the native platform for sending Android push notifications, but you can also use FCM as a push notification delivery service to notify users on Apple devices and the web too.
  • AWS Simple Notification Service (SNS) — SNS is the one service in this list that explicitly calls out the ability to send cross-channel notifications on top of push to SMS and other HTTPs compliant endpoints and services.
  • Expo Push – if you’re already building a React Native application with Expo, then Expo push can manage push notification delivery to APNS and FCM on your behalf with built-in credentials management.
  • Pusher Beams – a push service that manages notification delivery via APNS, FCM, and web push on your behalf.
  • PubNub Push - similar to Pusher Beams, this is also a push service that manages push delivery for you via APNS and FCM.

Pros

  • Push delivery services manage last-mile delivery for you. They’ll give you a mechanism to confirm that push notifications are making it to the downstream platform (i.e. APNs and FCM).

Cons

  • Push delivery services don’t provide orchestration logic. Most of the providers above operate as a simple pub-sub model, so if you’re looking to power more complex notification journeys you’ll need to build your own notification engine.
  • Push delivery services don’t handle template management. This means it remains a concern that lives in your codebase.
  • Push delivery services don’t handle delivery to non-push channels out of the box. And while AWS SNS can send SMS and email notifications, it doesn’t support in-app or 3rd-party chat apps such as Slack and Teams.

If you’re only going to be sending push notifications across iOS and Android and want a quick way to get started, we’d recommend reaching for one of the push services listed above. If you expect to send notifications across other channels in addition to push, you’ll want to look elsewhere.

Marketing automation and customer engagement platforms

Next are the tools that were introduced to help marketers automate promotional notifications and to help drive messaging sequences to top-of-funnel signups. Though these tools usually have some lightweight transactional/product notification capabilities, they’re limited to simple use cases such as forgotten password and welcome emails.

Some examples of products in this space include:

Pros

  • Customer engagement platforms are good for one-time, campaign-based messaging.
  • Customer engagement platforms are great for enabling non-engineering stakeholders to create notifications. The tools in this space have visual journey builders and template editors, making it easy for non-engineers to create the notifications that will go to end customers.

Cons

  • Customer engagement platforms have limited preference management. Preference management is usually limited to opt-out behavior, which means that to give customers more specific notification preference controls you’ll need to build it yourself.
  • Customer engagement platforms were built for marketers, not developers. For production-grade, transactional notifications, you won’t find a few key features in these products, such as multi-environment support and API-log-based debugging.

If you’re primary goal is to build a notification system to power your product notifications in production, we’d recommend steering clear of squeezing your transactional product notifications into a customer engagement platform as it will lead to a lot of rework later. That said, if you’re looking to run re-engagement campaigns or onboarding sequences, these tools can be a great way to go.

Third-party notification systems

The last category is the one we belong to here at Knock. These are a set of notification platforms that have emerged in the last two to three years to give engineering teams pre-built notification system components that they can use to compose into their own custom notification system.

The ideal third-party notification system should:

  • Be developer-first with a clear set of concepts and a well-documented API, since developers will be the ones working with its components.
  • Have a dashboard for non-technical users to see and manage templates, so developers don’t need to make copy changes on behalf of PMs.
  • Give teams the infrastructure they need to power real-time feeds of in-app notifications. Once the developer has to build a channel outside of the system, the system loses a lot of its value prop for the customer.
  • Provide a rich orchestration system to control complex rules around who should receive a notification, on which channel, and when
  • Be elastic and be able scale up as needed to meet the demands of bursty and unpredictable notification volume whilst guaranteeing reliable delivery.

Pre-built notification tools and the build-vs-buy question

Of course, the other option here is to build a system yourself and avoid any of the off-the-shelf options mentioned above. If you want to understand some of the elements that go into making a build-vs-buy decision for a notification system, it’s worth reading our blog post on the topic.

The tl;dr if you didn’t follow that link. The engineering costs with building and maintaining a notification system are high and hard to see from the outset. They include:

  • Building notification expertise across each of the channels you want to send to
  • Delivering a consistent product experience as your team grows and sees employee turnover
  • Increasing total cost of ownership as the product grows and adds new features, notification channels, and cross-channel send and preference logic

The ideal solution for a notification system should give teams the building blocks to address notification needs as they emerge on their roadmap, without them needing to sink the time into building all of those components themselves.

Introducing Knock: the developer-first notifications service

Knock is a notifications service built from the ground up for developers to power transactional product notifications.

With Knock, you can easily create new notifications and orchestrate exactly which channels those notifications should be sent on. Then in your backend code, you can call Knock to invoke that notification flow for a set of recipients you define. Knock will take care of applying specific preferences for each recipient, rendering templates for personalization, and reliably sending any generated notifications to a third-party provider.

import { Knock } from "@knocklabs/node";

const knock = new Knock(process.env.KNOCK_API_KEY);

async function createCommentAndNotifyUsers(post, commentText, author) {
  const comment = await saveCommentInDatabase(commentText, author);

  await knock.workflows.trigger("new-comment", {
    actor: author.id,
    recipients: post.participants.map((u) => u.id),
    data: {
      commentText: comment.text,
    },
  });
}

Here’s a 5-minute demo of creating cross-channel notifications with Knock:

Knock sends notifications across all channel types and integrates with a wide selection of providers that we’re adding to on a regular basis.

Every month, Knock gets better. We’re constantly adding features to make our workflow engine more powerful and reduce the amount of work it takes to create and maintain notifications.

Pros

  • Knock is a flexible, component-level system. By providing the components we listed at the top of this post on their own as concepts in the Knock model, you can pick and choose what you want to use for your notification system.
  • Knock powers composable in-app notifications infrastructure. This is a big win as it means you can power all notification channels from one channel, instead of using certain systems for out-of-app channels and managing in-app in a homegrown solution.
  • Knock provides a built-in dashboard for managing notifications. Knock comes with a pre-built dashboard out-of-the-box. This helps with general visibility into the system and also enables non-engineers to handle remedial tasks such as updating notification copy.
  • Knock handles reliable delivery and scales with you. Knock manages notification delivery with automated retries on your behalf, and scales with you with no infrastructure to manage.
  • Knock is observable. You can see everything that happens in Knock, from API request to workflow trigger to delivery attempts to downstream providers. We also provide extensions to observability tools such as Datadog so you can monitor Knock where you observe the rest of your stack.
  • Knock is built for developers. Knock has logically separated environments, a git-like commit flow, webhooks to synchronize system state, SDKs available in all major languages, and a well written set of documentation.

Cons

  • Knock doesn’t power the primary use cases handled by marketing automation tools. If you’re looking to build dynamic segments for one-time messages (think promotional campaigns or feature announcements), you’re better off with a marketing automation tool.

Notification systems: how to make the choice

If you’re sending simple notifications to a single channel, you can probably just call that provider directly from your application code. It’s fast and simple, and you can come back to your notification system choice when you add more channels and notification logic.

If you’re powering cross-channel notifications but don’t yet need to power in-app notifications and you’re a small team of primarily engineers, you’ll probably be fine with a backend library to start.

If the only channel you care about is push, use a push notification delivery service such as FCM.

If you’re looking to power notifications across multiple services or teams, and you’re starting to encounter a need for the components we discussed at the top of this post (delivery job queues, preference models, orchestration logic), you should try Knock! It’s as quick to get started as an open-source backend library, but will provide you a reliable, scaleable way in which to send your product notifications while also enabling less technical stakeholders to help maintain the system.

You can start on Knock for free, up to 10k notifications sent per month. The best place to start is to sign up for an account and read our docs. If you’d like to learn more, you can book a personalized demo today.