Building an Investor List App with Novu and Supabase
Tags: webdev, notifications, javascript, supabase
Published: 2024-04-22 09:22:46

Supabase is an impressive open-source alternative to Firebase. It provides multiple tools that enable you to build comprehensive backend systems, including features for authentication, databases, storage, and real-time updates.

In this article, I'll guide you through the process of integrating your Next.js Supabase app with Novu. This integration will allow for a seamless, quick, and stress-free notification experience. Novu is an open-source notification infrastructure designed to assist engineering teams in creating robust product notification experiences.

We'll create an **AI investor list app**. This app will enable users to add details of angel investors interested in funding AI startups at the Pre-seed, Seed, and Series A stages. It will also announce and send in-app notifications when new investors are added.

**Prerequisites**
-----------------

Before diving into the article, make sure you have the following:

- Node.js installed on your development machine.
- A Novu account. If you don’t have one, sign up for free at **[the web dashboard](https://web.novu.co/).**
- A Supabase account. If you don’t have one, sign up for [free at the dashboard](https://supabase.com/dashboard/projects).

If you want to explore the code right away, you can **[view the completed code](https://github.com/novuhq/novu-supabase-app)** [](https://github.com/novuhq/react-translation-app) on GitHub.

Set up a Next.js App
--------------------

To create a Next.js app, open your terminal, `cd` into the directory you’d like to create the app in, and run the following command:


```bash
npx create-next-app@latest supabase-novu
```
Go through the prompts:

![Prompts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ytjkv7e1da1fxue944p.png)

`cd` into the directory, `supabase-novu` and run to start the app in your browser:


```bash
npm run dev
```
Set up Supabase in the App
--------------------------

Run the following command to add Supabase to the project:


```bash
npm i @supabase/supabase-js
```
Ensure you have signed up and **created a new project** on Supabase. It takes a couple of minutes to have your project instance set up on the dashboard. Once done, it should look similar to the image below:


![Database created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/avke3svqiape36odicel.png)

We’ll go ahead to create a database table, **investors**, in our newly created project. Click on the **SQL Editor** on the dashboard and add the following SQL query to the editor.


```sql
CREATE TABLE investors (
 id bigint generated by default as identity primary key,
 email_reach text unique,
 name text,
 website text,
 funding_amount text,
 funding_type text,
 inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);

alter table investors enable row level security;

create policy "Investors are public." on investors for
   select using (true);
```
It should look like so:

![Alter table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kumg89w581ehuzql60he.png)

Click on **“Run”** to run the query, create the table and apply a Policy.

This will go ahead to create an `investors` table. It also enables **row level permission** that allows anyone to query for all investors. In this article we are not concerned with authentication. However, this is something you should consider if you are to make it a production grade app.

You should be able to see your table created successfully!


![Table created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unq3r696txojzk2qzre3.png)
Next, we need to connect our Next.js app with our Supabase backend.

Connect Supabase with Next.js
-----------------------------

Create a `.env.local` or `.env` file in the root of the Next.js app and add the following environment variables:


```sql
NEXT_PUBLIC_SUPABASE_URL=<insert-supabase-db-url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<insert-anon-key>
```

Please replace the placeholders here with the correct values from your dashboard.

Click on the **“Project Settings icon”** at the bottom left and click on **“API”** to show you the screen below to obtain the correct values.


![Project settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8mnhre0ba57y7n3viee.png)
Create an `utils` folder inside the `src` directory of our Next.js project. Then go ahead to create a `supabase.js` file in it. Add the code below:


```javascript
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);
```
This is the Supabase client that we can use anywhere in our app going forward. The `createClient` function that we imported from the supabase library makes it possible.

Next, let’s set up our UI components and wire them to the Supabase backend.

Build UI to Interact with Supabase
----------------------------------

Open up the `src/pages/index.js` file. We will modify it a lot.

Replace the content of the file with the code below:


```javascript
import Image from "next/image";
import { Inter } from "next/font/google";
import { supabase } from "@/utils/supabase";
import { useEffect, useState } from "react";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
  const [investors, setInvestors] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchAllInvestors();
  }, []);

  async function fetchAllInvestors() {
    try {
      setLoading(true);
      const { data, error } = await supabase.from("investors").select("*");

      if (error) throw error;
      setInvestors(data);
    } catch (error) {
      alert(error.message);
    } finally {
      setLoading(false);
    }
  }

  async function addInvestor(
    email_reach,
    name,
    website,
    funding_amount,
    funding_type
  ) {
    try {
      const { data, error } = await supabase
        .from("investors")
        .insert([
          {
            email_reach,
            name,
            website,
            funding_amount,
            funding_type,
          },
        ])
        .single();
      if (error) throw error;
      alert("Investor added successfully");
      fetchAllInvestors();
    } catch (error) {
      alert(error.message);
    }
  }

  function handleSubmit(e) {
    e.preventDefault();

    const email_reach = e.target.email_reach.value;
    const name = e.target.name.value;
    const website = e.target.website.value;
    const funding_amount = e.target.funding_amount.value;
    const funding_type = e.target.funding_type.value;

    addInvestor(email_reach, name, website, funding_amount, funding_type);
  }
  
  return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
    >
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
          NOVU SUPABASE DASHBOARD
        </p>
        <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
          <a
            className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
            href="<https://vercel.com?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
            target="_blank"
            rel="noopener noreferrer"
          >
            By{" "}
            <Image
              src="/vercel.svg"
              alt="Vercel Logo"
              className="dark:invert"
              width={100}
              height={24}
              priority
            />
          </a>
        </div>
      </div>

      <div class="grid grid-cols-2">
        <div className="mt-1 flex justify-center">
          <form onSubmit={handleSubmit}>
            <div>
              <label className="p-3 block">Name:</label>
              <input
                className="text-black p-2"
                type="text"
                name="name"
                required
                placeholder="Enter name"
              />
            </div>
            <div>
              <label className="p-3 block">Email Reach:</label>
              <input
                className="text-black p-2"
                type="text"
                name="email_reach"
                required
                placeholder="Enter investor email"
              />
            </div>
            <div className="mt-5">
              <label className="p-2 block">Website:</label>
              <input
                className="text-black p-2"
                type="text"
                name="website"
                required
                placeholder="Enter website"
              />
            </div>
            <div className="mt-5">
              <label className="p-2 block">Funding Amount (Up to X USD):</label>
              <input
                className="text-black p-2"
                type="text"
                name="funding_amount"
                required
                placeholder="Enter funding amount"
              />
            </div>
            <div className="mt-5">
              <label className="p-2 block">Funding Type:</label>
              <input
                className="text-black p-2"
                type="text"
                name="funding_type"
                required
                placeholder="Enter type of Funding"
              />
            </div>

            <button
              type="submit"
              className="bg-blue-600 p-2 rounded-md mt-5 px-12"
            >
              Submit Investor Details
            </button>
          </form>
        </div>

        <div className="mt-1 flex justify-center">
          {investors?.length === 0 ? (
            <div>
              <p>There are no investors yet</p>
            </div>
          ) : (
            <div>
              <p className="mb-5">Here are the investors available: </p>
              <table>
                <thead>
                  <tr>
                    <th>Name </th>
                    <th>Email Reach</th>
                    <th>Website</th>
                    <th className="p-3">Funding Amt</th>
                    <th>Funding Type </th>
                  </tr>
                </thead>
                <tbody className="">
                  {investors?.map((item) => (
                    <tr key={item.id}>
                      <td>{item.name}</td>
                      <td>{item.email_reach}</td>
                      <td>{item.website}</td>
                      <td>{item.funding_amount}</td>
                      <td>{item.funding_type}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>
      </div>

      <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
        <a
          href="<https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            Docs{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
            Find in-depth information about Next.js features and API.
          </p>
        </a>

        <a
          href="<https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            Learn{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
            Learn about Next.js in an interactive course with&nbsp;quizzes!
          </p>
        </a>

        <a
          href="<https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            Templates{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
            Discover and deploy boilerplate example Next.js&nbsp;projects.
          </p>
        </a>

        <a
          href="<https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            Deploy{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
            Instantly deploy your Next.js site to a shareable URL with Vercel.
          </p>
        </a>
      </div>
    </main>
  );
}
```
Let’s go over some sections of this code block:


```javascript
function handleSubmit(e) {
    e.preventDefault();

    const email_reach = e.target.email_reach.value;
    const name = e.target.name.value;
    const website = e.target.website.value;
    const funding_amount = e.target.funding_amount.value;
    const funding_type = e.target.funding_type.value;

    addInvestor(email_reach, name, website, funding_amount, funding_type);
  }
```

This function handles the submission of the form. It grabs the value from the form fields and passes it to the `<span style="background-color: initial; font-family: inherit; font-size: inherit; color: initial;">addInvestor</span>` function.

```javascript
async function addInvestor(
    email_reach,
    name,
    website,
    funding_amount,
    funding_type
  ) {
    try {
      const { data, error } = await supabase
        .from("investors")
        .insert([
          {
            email_reach,
            name,
            website,
            funding_amount,
            funding_type,
          },
        ])
        .single();
      if (error) throw error;
      alert("Investor added successfully");
      fetchAllInvestors();
    } catch (error) {
      alert(error.message);
    }
  }
```

The `addInvestor` function takes in the right arguments and makes a query to supabase to insert the values into the `investors` table. This function also calls the `fetchAllInvestors()` function that retrieves all data from the `investors` table.


```javascript
async function fetchAllInvestors() {
    try {
      setLoading(true);
      const { data, error } = await supabase.from("investors").select("*");

      if (error) throw error;
      setInvestors(data);
    } catch (error) {
      alert(error.message);
    } finally {
      setLoading(false);
    }
  }

```

As you can see from the code above, the `fetchAllInvestors()` function makes a SELECT query to the `investors` table to return and all the investors to the frontend.


```javascript
const [investors, setInvestors] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
  fetchAllInvestors();
}, []);
```
When the page loads up, it calls the `fetchAllInvestors()` function by default.

Run your app with `npm run dev`. It should look like this:


![Run app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpnbixfv09d88ldubp2m.png)
Add some investor details and submit. Your app should look like this now:


![Add items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spe11ln9r0yh3fr1op0x.png)
As we add items, they are saved to the Supabase database and displayed on the screen.

Inspect the table on Supabase. The data should appear as follows:


![Inspect table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9olafsn0szamyzwqteai.png)
Next, we'll set up notifications in the app. Our goal is to alert everyone when a new investor is added to the database. This is where Novu comes into play!

Set up Novu in the App
----------------------

Run the following command to install the Novu node SDK:


```javascript
npm install @novu/node

```
Run the following command to install the Novu Notification Center package:


```javascript
npm install @novu/notification-center
```
The Novu Notification Center package provides a React component library that adds a notification center to your React app. The [package is also available for non-React apps](https://docs.novu.co/notification-center/introduction#ui-libraries).

Before we can start sending and receiving notifications, we need to set up a few things:

2. Create a workflow for sending notifications,
5. Create a subscriber - recipient of notifications.

**[](https://docs.novu.co/quickstarts/react#create-a-workflow)Create a Novu Workflow**
--------------------------------------------------------------------------------------

A workflow is a blueprint for notifications. It includes the following:

- Workflow name and Identifier
- Channels: - Email, SMS, Chat, In-App and Push

Follow the steps below to create a workflow:

2. Click **Workflow** on the left sidebar of your Novu dashboard.
5. Click the **Add a Workflow** button on the top left. You can select a Blank workflow or use one of the existing templates.
8. The name of the new workflow is currently **“Untitled”**. Rename it to `notify users of new investors`.
11. Select **In-App** as the channel you want to add. 

![Select In-App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fu5iwb3wyyr6n7juylu.png)
14. Click on the recently added **“In-App”** channel and add the following text to it. Once you’re done, click **“Update”** to save your configuration.


![Payload variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lx7w1dorik7ywcgndocx.png)
The `{{name}},{{funding_amount}}` and `{{email}}` are custom variables. This means that we can pass them to our [payload](https://docs.novu.co/quickstarts/general#pass-the-subscriber-information-in-the-trigger-quickest) before we trigger a notification. You’ll see this when we create the API route to send notifications.

**[](https://docs.novu.co/quickstarts/react#create-a-subscriber)Create a subscriber**
-------------------------------------------------------------------------------------

If you click **“Subscriber”** on the left sidebar of the **[Novu dashboard](https://web.novu.co/subscribers)**, you’ll see the subscriber list. As a first time Novu user, it will be an empty list. Subscribers are your **app users.**

<hr />


![Subscribers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mji1osf9smwl1dcz4fnn.png)
Open your terminal and run the following script to create a subscriber:


```bash
curl --location '<https://api.novu.co/v1/subscribers>' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'Authorization: ApiKey <NOVU_API_KEY>' \
  --data-raw '{
    "firstName": "John",
    "lastName": "Doe",
    "email": "johndoe@domain.com",
    "phone": "+1234567890"
    }'
```
**Note:** You can get your NOVU API Key from the [settings section](https://web.novu.co/settings) of your Novu dashboard.

Refresh the Subscribers page on your Novu dashboard. You should see the recently added subscriber now! You can also add a subscriber to Novu by running this [API endpoint](https://docs.novu.co/api-reference/subscribers/create-subscriber).

The best option to add a subscriber is via code in your backend. With Node.js code, you can run the following code to create a subscriber:


```javascript
import { Novu } from "@novu/node";

// Insert your Novu API Key here
const novu = new Novu("<NOVU_API_KEY>");

// Create a subscriber on Novu
await novu.subscribers.identify("132", {
  email: "john.doe@domain.com",
  firstName: "John",
  lastName: "Doe",
  phone: "+13603963366",
});
```

**Set up Novu Notification Center in the App**
----------------------------------------------

Head over to `scr/pages/index.js`. We’ll modify this page again to include the following:

- Import and display the Novu Notification Center.
- A function to trigger the notification when a new investor is added.

Import the Notification Center components from the Novu notification center package like so:


```javascript
...
import {
  NovuProvider,
  PopoverNotificationCenter,
  NotificationBell,
} from "@novu/notification-center";
```
Display the notification center by adding the imported components like so:


```javascript
...
return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
    >
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
          NOVU SUPABASE DASHBOARD
        </p>
        <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
          <NovuProvider
            subscriberId={process.env.NEXT_PUBLIC_SUBSCRIBER_ID}
            applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
          >
            <PopoverNotificationCenter>
              {({ unseenCount }) => (
                <NotificationBell unseenCount={unseenCount} />
              )}
            </PopoverNotificationCenter>
          </NovuProvider>
        </div>
      </div>

      ...
      <div className="grid grid-cols-2">
```

Now, your app should display a notification bell. When this bell is clicked, a notification user interface will pop up.

**Note:** The [NovuProvider root Component ships with many props](https://docs.novu.co/notification-center/client/react/api-reference#novuprovider) that can be used to customize the Notification Center to your taste. The floating [popover component](https://docs.novu.co/notification-center/client/react/api-reference#popovernotificationcenter) that appears when clicking on the **[NotificationBell](https://docs.novu.co/notification-center/client/react/api-reference#notificationbell)** button. It renders the **[NotificationCenter](https://docs.novu.co/notification-center/client/react/api-reference#notificationcenter)** component inside its content.


![Notification Center](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgy2fs76t5e4m3ta0t7x.png)
Add the following Novu env variables to your `.env.local` file:


```javascript
NEXT_PUBLIC_SUBSCRIBER_ID=
NEXT_PUBLIC_NOVU_APP_ID=
NEXT_PUBLIC_NOVU_API_KEY=
```

The **Novu API Key** and **APP ID** can be found in the [Settings](https://web.novu.co/settings) section of your Novu dashboard.


![Novu credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewiecizgy4de1cchx3da.png)
The Subscriber ID too is on your dashboard. Earlier in this article, we created a subscriber. We need that ID now.

**Note:** Adding the Subscriber ID to the env file is only for the purpose of this article. Your subscriber ID is the user ID obtained from an authenticated user. When a user logs in, their ID should be the value passed to the **subscriberId** property of the **NovuProvider** component.

Develop a Simple API to Trigger Notifications
---------------------------------------------

Create a `send-notification.js` file in the `src/pages/api` directory. Add the code below to it:


```javascript
import { Novu } from "@novu/node";

const novu = new Novu(process.env.NEXT_PUBLIC_NOVU_API_KEY);

export const workflowTriggerID = "notify-users-of-new-investors";

export default async function handler(req, res) {
  const { email, name, amount } = JSON.parse(req.body);

  await novu.trigger(workflowTriggerID, {
    to: {
      subscriberId: process.env.NEXT_PUBLIC_SUBSCRIBER_ID,
    },
    payload: {
      name: name,
      funding_amount: amount,
      email: email,
    },
  });

  return res.json({ finish: true });
}

```
The `workflowTriggerID` value is obtained from the workflow dashboard. Earlier, when we set up a workflow titled `notify users of new investors`, Novu created a slug from this title to serve as the trigger ID.

You can see it here:


![Workflow Trigger ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7btbqymytzzeraw42kqs.png)
The block of code below triggers the notification via the Novu SDK:

- Accepts the workflow trigger ID to determine which workflow to trigger.
- Accepts the subscriber ID value to identify the notification recipient.
- Accepts a payload object that represents the parameters to inject into the workflow variables.


```javascript
await novu.trigger(workflowTriggerID, {
    to: {
      subscriberId: process.env.NEXT_PUBLIC_SUBSCRIBER_ID,
    },
    payload: {
      name: name,
      funding_amount: amount,
      email: email,
    },
  });
```
Next, we need to set up one more thing on the frontend before we test the notification experience in our app.

Add a Notification Function to Index.js
---------------------------------------

Create a function inside the `Home()` function to call our recently created API like so:


```javascript
async function triggerNotification(email_reach, name, funding_amount) {
    await fetch("/api/send-notification", {
      method: "POST",
      body: JSON.stringify({
        email: email_reach,
        name: name,
        amount: funding_amount,
      }),
    });
  }
```
Call the `triggerNotification` function just after the `addInvestor` function within the `handleSubmit` function.

Your `handleSubmit` function should now appear as follows:


```javascript
function handleSubmit(e) {
    e.preventDefault();

    const email_reach = e.target.email_reach.value;
    const name = e.target.name.value;
    const website = e.target.website.value;
    const funding_amount = e.target.funding_amount.value;
    const funding_type = e.target.funding_type.value;

    addInvestor(email_reach, name, website, funding_amount, funding_type);
    triggerNotification(email_reach, name, funding_amount);
  }
```
Run your app again and submit a new investor detail. You should get an instant In-App notification with the details of the new investor.


![Step 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7jy3q0q2n9glw2ihsqmt.png)

![Step 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn4jxerpo1vcm39b071w.png)

A Step Further - Notify All Investors
-------------------------------------

We currently have a database containing investor emails. This is valuable data. Let's consider adding a feature to email these investors about new investment rounds from startups seeking funding.

We will not add a UI for the save of brevity. You can add a UI as an improvement and challenge.

Create a new file, `email-investors.js` within the `pages/api` directory.

Add the code below to it:


```javascript
import { Novu } from "@novu/node";
import { supabase } from "@/utils/supabase";

const novu = new Novu(process.env.NEXT_PUBLIC_NOVU_API_KEY);

export const workflowTriggerID = "new-opportunities";

export default async function handler(req, res) {
  /**
   * Grab all investors
   */
  const { data, error } = await supabase.from("investors").select("*");

  /**
   * Map into a new array of subscriber data
   */
  const subscriberData = data.map((item) => {
    return {
      subscriberId: item.id.toString(),
      email: item.email_reach,
    };
  });

  const extractIDs = (data) => {
    return data.map((item) => item.subscriberId);
  };

  const subscriberIDs = extractIDs(subscriberData);

  /**
   * Bulk create subscribers on Novu
   */
  await novu.subscribers.bulkCreate(subscriberData);

  /**
   * Create a Topic on Novu
   */
  await novu.topics.create({
    key: "all-investors",
    name: "List of all investors on our platform",
  });

  /**
   * Assign subscribers to Topic
   */
  const topicKey = "all-investors";

  /**
   * Add all investors to the Topic
   */
  await novu.topics.addSubscribers(topicKey, {
    subscribers: subscriberIDs,
  });

  /**
   * Trigger workflow to the Topic
   */
  await novu.trigger(workflowTriggerID, {
    to: [{ type: "Topic", topicKey: topicKey }],
  });

  return res.json({ finish: true });
}

```
There are a series of events happening in this file. I’ll explain the steps below:

2. Fetch all investors data from Supabase
5. Return an array of objects of the supabase data with “subscriberId” and “email”.
8. Create all the investors as subscribers on Novu. Thankfully Novu has a `bulkCreate" function to create hundreds of subscribers at once instead of doing a complex loop yourself.
11. Create a topic on Novu. Topics offers a great way of sending notifications to multiple subscribers at once. In this case, it’s perfect for us. Here, we created a “all-investors” topic and then proceeded to add all the investors as subscribers to the topic.
14. The last step is what triggers the notification to all the investors. This is a simple syntax that triggers notifications to a topic. This means every subscriber belonging to that topic will receive a notification!

This block of code is where I set the trigger ID of the new Email workflow i created.

```javascript
 export const workflowTriggerID = "new-opportunities";
```

If you haven’t, go ahead and create an email workflow on Novu. Name the workflow and use the corresponding trigger ID like I did.


![Email workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x13a7hjico24vg5lxfqb.png)
Add content to the email workflow and save.

![Email content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/coubrfoo7u2t1vas9k79.png)

**Note:** At this stage, it's crucial to ensure that an email provider has been selected from the **Integrations Store.** This allows Novu to identify the email provider to use for delivering the email.

Run the API, `/api/email-investors`. The investors should get an email like this:


![Novu Email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjhfmk1pg22xszuv2brp.png)

Conclusion
----------

The combination of two robust open-source tools, Novu and Supabase, enables the rapid development of apps with a rich notification experience.

I hope you enjoyed building this simple investor list app as much as I did. Novu offers different channels such as Email, SMS, and Push, gives you a [detailed activity feed](https://docs.novu.co/activity-feed/introduction#viewing-a-specific-subscribers-activity) to help debug your notifications and much more!

Do you have an idea or an app you'd like to build with Novu and Supabase? We are excited to see the incredible apps you'll create. Don't hesitate to ask any questions or request support. You can find us on [Discord](https://discord.gg/novu) and [Twitter](https://twitter.com/novuhq). Please feel free to reach out.