Creating A Hot New Food Delivery App with Novu

James Sinkala - Apr 24 '23 - - Dev Community

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.

Patrick

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? 🚀

JOIN HERE

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 NameNotification Description, and Notification Group, which is set to General by default.

Image description

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.

Image description

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:

Image description

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:

Image description

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
})
Enter fullscreen mode Exit fullscreen mode

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,
    });
Enter fullscreen mode Exit fullscreen mode

After having registered some users, we should see the following when visiting the Activity Feed section of the Novu dashboard:

Image description

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 Descriptionto "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>
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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!',
    }
})
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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]
    })
Enter fullscreen mode Exit fullscreen mode

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!

Image description

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
        }
    });
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
    }
});
Enter fullscreen mode Exit fullscreen mode

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]
});
Enter fullscreen mode Exit fullscreen mode

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.

ConnectNovu

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .