TL;DR
In this tutorial, we’ll create a tool to manage a notification system for a food delivery app. To do so, we’ll use the power of the open source notification infrastructure from Novu alongside our custom platform and PostMark.
Join the ConnectNovu Hackathon
Showcase your skills, push the boundaries of innovation, and meet like-minded community members.
ConnectNovu Hackathon is a global event focused on notifications. If you love playing with In-App, SMSs, Push notifications, Emails, and chats, that's the place for you!
Are you ready to build the next big thing? 🚀
The Kick-Off
We all love the new era of delivery apps. No calls, complete your order on your phone in minutes, pay a huge delivery fee, and then eagerly wait until our food arrives at our door.
But in this process, those delivery apps keep you in the loop for the entire operation — from order placed to package delivered. But to do so, you’ll need a robust notification system to take care of it.
In this tutorial, we will learn how to create a notification management system for a food delivery app using Novu, a powerful open source notification infrastructure.
If you want to jump to the source code for this tutorial, check it out here.
Jumping Into Business
For this tutorial, we’ll just start with a simple app named Foody. We’ll create the project with Nuxt and Supabase, but you can tag along with any JavaScript framework you’d like. You’ll also need the LTS version of Node.js too.
Typically, there are a set of notifications we’d expect to receive in any e-commerce service, and the food delivery system is no exception. For Foody, we’ll need at least the following types of notifications:
- Customer hospitality notifications: Welcoming and thanking users.
- Service-specific notifications: - Order placement, delivery status, feedback requests, promotions.
- Legal and security updates notifications: Terms and privacy, etc.
Let’s see how Novu can handle some of these notifications in a food delivery service context.
Getting Our Project Started
We’ll need to sign up for a Novu account and start setting up our project.
After that, we must set up at least one delivery provider. That delivery provider will be used to manage all of our notifications within our app. So head over to the Integration Store to see our options.
At the time of writing, Novu supports three notification channels: email, SMS, and an in-app notification center. If you want to create a notification provider other than the provided ones, check out their documentation.
We’ll set up a new provider using Postmark for Foody. Let’s go ahead and do this.
Setting Up PostMark
Starting off, we’ll need a PostMark account too. Once that’s done, we’ll need to verify a sender and create a new Server API Token if one doesn’t already exist.
Back at the Novu Integration Store page, click connect on Postmark, fill in the Server API token
as the API Key
on the form presented, and set from email
to the sender verified on Postmark. Lastly, provide a sender name
, toggle the provider to active, and click the connect button.
Creating Notifications on Novu
To create a new notification on Novu, click the Notifications page and the New button on the top-right side. Fill in the notification settings for the new template by providing the Notification Name
, Notification Description
, and Notification Group
, which is set to General by default.
Send A Welcome Notification
For Foody, we’ll set up a customer welcoming email to demonstrate how to set up notifications in Novu. Fill in the fields in the previous figure, setting the Notification Name
to welcome-new-user
and the Notification Description
to Notification for welcoming newly registered users
.
Proceed to the new notification's Workflow Editor tab to add steps. In our case, we want to send an email notification welcoming the customer and then receive an in-app admin dashboard notification for them. So, on this page, drag the email step to the canvas, followed by the in-app step.
Select the email step currently inside the canvas and edit the template. You can use the visual template editor or write custom markup for email channels. We’ll be using the template editor:
The Steps Variables section on the right side of the editor lists the variables we’ve used inside the email template in the handlebars - {{}}
. These variables will be populated by the submitted email payload sent through a Novu trigger. You can preview and test the created email notification for troubleshooting on completion.
Back in the workflow editor, select the In-App step and edit its template too:
Call this trigger after the user account validation to trigger this notification inside Foody.
Integrating Novu into Foody
If you followed the Nuxt app provided for this tutorial, the following logic has been set up inside a server API endpoint file - /server/api/register-notifications-subscriber.js
. This notification serves two purposes, the first being the registration of the app user as a Novu notifications subscriber and the second being the sending of the welcoming email.
// dependency imports
export default defineEventHandler(async (event) => {
// variable declarations
// Send welcome notification to new user
const [firstName, lastName] = user.user_metadata.full_name.split(" ");
await novu.trigger('welcome-new-user', {
to: {
subscriberId: user.id,
email: user.user_metadata.email,
firstName,
lastName,
phone: user.user_metadata.phone,
},
payload: {
firstName
},
});
// other code
})
Despite not using it in the email template, we are providing as much subscriber data as possible to the trigger in the above code. Why? Because when the trigger is called, Novu upserts the data to a new subscriber, and if none exists, it creates a new one.
To explicitly create a new subscriber, use this:
await novu.subscribers.identify(user.id, {
email,
firstName,
lastName,
phone: user.user_metadata.phone,
});
After having registered some users, we should see the following when visiting the Activity Feed section of the Novu dashboard:
The activity feed monitors all outgoing messages associated with a Novu project. It can monitor activity and discover potential issues with providers or channels.
Sending Order-Specific Notifications with Novu
Now that we’ve seen how it all works for sending welcome emails to our Foody users, let’s do it again for our actual orders!
Sending Order Confirmations
Like any other food delivery app, we want to keep our customers in the loop. So next, we’ll need to set up a notification that confirms their order once it’s been placed.
Create a new notification following the previous steps, setting the Notification Name
to "order-confirmation"
and the Notification Description
to "Notifying users on order placement"
.
For this notification, we would like to send a list of dishes the user purchased. Hence the suitable way to write the email template would be using the following custom markup:
<div>
Hello {{subscriber.firstName}},
</div>
<div style="padding: 4px">
You successfully placed order number #{{orderNumber}}.
</div>
<ul style="padding: 4px">
{{#each dishes }}
<li>{{count}} x {{name}} </li>
{{/each}}
</ul>
<div style="padding: 4px">
Delivery Fees: {{deliveryFee}}$
</div>
<div style="padding: 4px; font-weight: 800">
Total: {{totalFee}}$
</div>
<div style="padding: 4px">
Please notify us if any dish is out of order.
</div>
We can see two new things in the above template that differentiate it from the email templates we created earlier.
First, we have the subscriber.firstName
variable. Novu manages an app’s users in a specific subscribers data model, which allows the API to manage different aspects of the notification flow while providing an easy interface for triggering notifications. The subscriber.firstName
variable comes from the user data point of the subscriber object allowing its placement inside the notification template. Other data we can obtain in the user data point include the lastName
and avatar
.
The second part that is different from the previous templates is the iteration through the dishes
array. This is made possible by using the handlebars #each
helper. We also have access to the conditional #if
handlebars helper, which we’ll demonstrate its usage in coming sections of notifications.
To send the order-confirmation notifications to our Foody customers using the created template, add the following trigger:
// imports and global functions
export default defineEventHandler(async (event) => {
// variable declarations
// order creation logic providing us with orderDetails variable
await novu.trigger('order-confirmation', {
to: {
subscriberId: user.id,
email: user.user_metadata.email
},
payload: {
dishes: order.dishes,
orderNumber: orderDetails.order_number,
deliveryFee: order.deliveryFee,
totalFee: order.totalFee
},
});
// other code
})
Sending Delivery Status Notifications
To keep customers in the loop, we’d like to notify them of changes to the delivery status of the orders they place. Create a new delivery-status
notification, and use the following code as its template:
<div>
Hello {{subscriber.firstName}},
</div>
<div style="padding: 4px">
You order #{{orderNumber}} has been {{status}}.
</div>
<div style="padding: 4px">
Click the following link to give us a feedback of your order.
{{#if delivered}}
<p>
<a href="{{feedbackUrl}}" target="_blank" style="padding: 2px">
Give us feedback
</a>
</p>
{{/if }}
</div>
Delivery status notifications can be “order picked up”, “ order delivered”, and more. When the order has been delivered, with the assistance of the handlebars #if
helper, we can also send the user a link to give us feedback on the delivery.
The last single subscriber-based notification we would create for Foody is a feedback appreciation notification. We can use the template editor to make this notification since it’s less complex than the last two.
The following code can be used as the trigger for the notification within the app:
// imports
export default defineEventHandler(async (event) => {
// Declarations and user feedback database-submission logic
await novu.trigger('feedback-appreciation', {
to: {
subscriberId: user.id,
email: user.email
},
payload: {
firstName
},
});
return {
message: 'Appreciated user!',
}
})
Sending Bulk Subscription-based Notifications With Novu Topics
What about when we need to send bulk notifications to a group or all customers? We want to send notifications in bulk for things like promotions, discounts, service updates, and terms and services changes. How do we do this without performing the expensive practice of looping through data?
Novu provides an answer for this pain in the form of Topics. Novu Topics allow us to send bulk notifications to a group of users based on any criteria we define. With Topics, we can send multiple emails to users based on their interests, subscriptions, or any other possible attribute that can be determined.
In our scenario, we may want to send promotional and discount notifications to customers based on the dishes they frequently buy. For instance, if we equate a customer buying a particular dish a certain number of times to mean they like it, we’d add them to a topic that includes customers who like that dish. We might name such a topic as customers:favorite:dish_id
.
Adding this sort of functionality to our app will allow us to deliver targeted ads for some of our user bases instead of relying on a shotgun approach to customer engagement.
Creating a Novu Topic
Now that we understand what topics are in Novu let’s learn how to create one.
We will create a topic for Foody that sends promotional notifications to customers based on purchasing behavior, just like we discussed earlier. But before assigning subscribers to a topic, we need to create the topic first.
In our food delivery service scenario, a topic must be created whenever a dish is added to the app’s database. The following piece of topic-creating code would be ideally placed just after the logic that submits a new dish to the database:
// Dish data submission logic
const result = await novu.topics.create({
key: "customers:favorite:" + newDishData.id,
name: "Customers that like " + newDishData.name
});
Remember, the Novu topic key should be unique and can’t be changed once created.
Adding New Subscribers And Sending Notifications
To add a new subscriber to a topic, we’ll need to use the addSubscriber()
method:
// Order placement logic
// When customer fulfils criteria to be added to topic involving dish[0]
const result = await novu.topics.addSubscribers(
'customer:favorites:' + order.dishes[0].id,
{
subscribers: [user.id]
})
Inside the app, this should be called just after the order placement code. Users will be added to a topic if they fulfill the criteria.
Having created a topic and added some subscribers, the next step would be sending notifications to its subscribers. First, create a notification for the topic in question, such as dish-based-promotional-notification
. Don’t forget to add a template for this too!
Finally, create a trigger that sends the notifications. Novu’s topic notifications trigger is the same as the one used to send notifications to single subscribers. The difference is that, on the to
field, instead of placing the single subscriber's identity, we pass an array of topics whose subscribers we’d want to receive the notifications.
Here is a trigger example for the created notification:
import { Novu } from '@novu/node';
import { TriggerRecipientsTypeEnum } from '@novu/shared'
export default defineEventHandler(async (event) => {
// initiate Novu
const result = await novu.trigger('dish-based-promotional-notification',{
to: [{ type: TriggerRecipientsTypeEnum.TOPIC, topicKey }],
payload: {
promotionTitle,
promotionDetails
}
});
})
Customizing Notification Options
Not all customers would like to receive every kind of notification from the food delivery service. Therefore, we should allow them to opt out of some topics. What we’ll be doing is merely subscriber management within specific topics.
To demonstrate this, we’ll go to the dish-based-promotional-notification
notification template that we created earlier and add an unsubscribe link at the bottom. Since it’s not feasible to add a link in the template editor, convert the whole template to custom code with the following markup:
<div>
Hello {{subscriber.firstName}},
</div>
<div style="padding: 4px">
We have an offer you wouldn't want to miss.
</div>
<div style="padding: 4px">
{{promotionDetails}}
</div>
<div style="padding: 4px">
<a href="{{optOutLink}}?userId={{subscriber.subscriberId}}&topicKey={{topicKey}}" target="_blank">Click here</a> to unsubcribe from these emails.
</div>
We need their subscriber ID and the topic key to remove a subscriber from a topic, as demonstrated in the above markup. Since we never passed the topic key in the trigger involving this notification, we need to make the following changes to it:
const result = await novu.trigger('dish-based-promotional-notification',{
to: [{ type: TriggerRecipientsTypeEnum.TOPIC, topicKey }],
payload: {
promotionTitle,
promotionDetails,
topicKey
}
});
Now, we can proceed to create a topic-opting-out trigger
for users. As explained, we are simply removing the subscriber from the topic. To do that, we need to call the removeSubscribers()
method as follows.
const result = await novu.topics.removeSubscribers(topicKey, {
subscribers: [userId]
});
The Wrap-Up
It turns out a lot is going on behind the scenes of these food delivery apps! While we don’t have a fully functioning Foody app, we do have an excellent notification infrastructure in place, thanks to Novu! We used Supabase to handle our user authentication, but the most significant part was adding in some notification functionality.
If you liked what you saw here, let us know in the comments below! It’s been a pleasure, and I hope you take Foody to new heights!
P.S ConnectNovu Hackathon is live, everybody is invited to register, and there are some cool prizes too, check it here.