Watch a 5 Minute Demo

What is your company email address?
What is your country/permanent residence?
In which state do you live?
Privacy Policy - By signing up, I agree with Iterable's Privacy Policy. I understand that I am signing up to Iterable Marketing emails and I can unsubscribe at any time.
Form footer image
What is your first name?
What is your last name?
What is your company email address?
What is your company's name?
What is your country/permanent residence?
In which state do you live?
Privacy Policy - By signing up, I agree with Iterable's Privacy Policy. I understand that I am signing up to Iterable Marketing emails and I can unsubscribe at any time.

Schedule a demo to learn more.

What is your country/permanent residence?
Please provide how many emails are you sending per month
Please provide your current Email Provider
Privacy Policy - By signing up, I agree with Iterable's Privacy Policy. I understand that I am signing up to Iterable Marketing emails and I can unsubscribe at any time.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Form footer image
Thank you !

Thanks for contacting us, we’ll be in touch shortly.

Implementing the Notification Center

Marketers use Iterable to launch a variety of powerful campaigns and workflows. There are a lot of complex moving parts and they would ideally be able to know the status/completion of certain actions and be warned or alerted about errors that occur.

For instance, they might want to know when a workflow webhook they have setup is returning error codes—and that could affect millions of users going through that workflow.

The goal of the Notification Center is to provide an actionable, dynamic feedback system that alerts Iterable users.

This post covers some of the key design and engineering decisions. The code has been simplified to illustrate the main points made in this blog post.

Notification Center Design Decisions

We did some user story research and ended up deciding that:

  • Notifications should be dynamic and updatable. We would want one notification reporting on 5,000 workflow webhook errors over 5,000 separate workflow webhook notifications.
  • Notifications are at the Iterable Project level, but since there could be many users per Project, their read/unread statuses’ for each notification should be isolated. User 1 reading notification A, should not make that notification to be marked as read for User 2.
  • Notifications should be easy to create from an engineering perspective.
  • Notifications should know how to update themselves (more on this in the fingerprinting section)

Version 1 – Naive Version

This is the naive Notification Center prototype. It works but is clearly deficient in many ways.

  • You can’t update (or add new information) into an existing Notification easily as there’s no easy way to parse what is already in there (since title and description are Strings).
  • Secondly, if it references Iterable data such as a List or Workflow, the name of that List or Workflow could change so the description might end up being something like “Your Old List Name list has been updated”.
  • Lastly, there’s no way of mapping multiple notifications to to the same event and just updating an occurrence count.

Version 2 – Notification Context

From the main Iterable app, we wanted to make it as easy as possible to notify the notification center service of anything new. So the interface looks something like this:

We iterated on this and introduced a NotificationContext that is an interface that each type of notification will generate. Many of its functions take in the Notification parent with the metadata so we can dynamically generate things like the description based on all the data available to us.

Fingerprinting and Updating

One key is the introduction of fingerprinting our notifications so we know what “maps” to the same notification. In other words, we decide on the granularity of each notification. For instance, let’s say we have many workflow webhook errors going on, and each webook has a different URL but they are all part of the same workflow.

Now we can do something like:

This would generate a notification per workflow, per project. As seen from the code above, it knows how to update itself so that when the fingerprints match, we know that we are updating an existing notification and not creating a new one, and the NotificationContext knows how to update itself.

Dynamic Rendering of Description and Title

Among other fields, the description and title are now functions which means we have separated the persistence of data the notification needs, from the transformation of that into a human readable string. The only downside is that we need to store things like the workflow name.

The other downside is that perhaps the user might delete some of the WorkflowCampaignIds we reference, so the resulting notification can be invalid in the sense that it describes or references deleted / non-existent data.

Latest Version

But we still faced the issue of how we would be able to make sure that any references to Iterable data (which could change) was up to date. Referenced names and counts of Lists and such could change so we needed a way to dynamically retrieve that data and couldn’t store it just as Strings.

The solution was to introduce a rendering step. So upon getNotifications(), we would get the notifications from the database, then render them before returning them to the front-end.

Iterable Dependencies

We introduce an explicit dependencies field where we note what data we need to fetch from Iterable.

We also add in a rendering service class that does the actual rendering. In short, it fetches the dependencies needed for any notification. If the dependencies cannot be fetched or is found to be deleted, we mark that notification as invalid and delete it.

For instance, a notification may reference a campaign that was deleted, so that notification should be deleted. We want our notifications to not only be dynamic in being able to update itself, but accurately reflect the state of Iterable data as well.

Accurate Dynamic Rendering

Now, the data we need to persist for capturing the statistics of a notification is completely separate from our decision in how to render that. Now in our WorkflowWebhookNotificationContext we can refer to the latest workflow name and other properties based on the fetched dependencies.

Other Considerations

We knew that this design was going to lead to a high write, low read load on our Postgres database. Upon IterableNotificationCenterService.notify(), many of those calls may map to the same stored notification (same fingerprint) so we could have dirty reads or lost writes.

There are other optimizations we could make in the future, but at the time we decided it was best to have less captured notifications than to allow for invalid notifications (lost writes, where the data in the notification isn’t consistent) so we used a transaction level of repeatable reads for all update notification calls.

There are other features of the notification center as well (such as being able to change the notificationLevel from warning to errors based on custom thresholds…etc.) but at a high-level, we hope this shows how we went from generating static, string based notifications to dynamic, actionable notifications that help the user gain visibility and control as well as make it easy for engineers to add new notification classes or modify existing ones.

Search Posts