🚀 Top 4 ways to send notifications about new stars ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
Tags: webdev, programming, tutorial, react
Published: 2023-11-21 13:11:59

# TL;DR

In the last article, I discussed creating a [GitHub stars monitor](https://dev.to/triggerdotdev/take-nextjs-to-the-next-level-create-a-github-stars-monitor-130a).
In this article, I want to show you how you can be informed about new stars daily.
We will learn:

- How to build a generic system to create and use providers.
- How to use the providers to send notifications.
- Different use cases of using different providers.


![Notifications](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uwpjomw3pbrpq885q8z.gif)

<hr />

## Your background job platform 🔌

[Trigger.dev](https://trigger.dev/) is an open-source library that enables you to create and monitor long-running jobs for your app with NextJS, Remix, Astro, and so many more!

&nbsp;

[![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev)

Please help us with a star 🥹.
It would help us to create more articles like this 💖

{% cta https://github.com/triggerdotdev/trigger.dev %} Star the Trigger.dev repository ⭐️ {% endcta %}

<hr />

## Let’s set it up 🔥

We are going to create different providers to inform us when there are new stars. We will set up `Email`, `SMS`, `Slack`, and `Discord` notifications.

Our goal is to make it simple enough for every contributor to contribute more providers in the future.

Each provider will have a different set of parameters, some only `API keys` and some phone numbers, depending on the providers.

To validate those keys, let’s install `zod`; it’s a great library to define schemas and check data against them.

You can start by running:

```jsx
npm install zod --save
```

Once that is done, create a new folder called `providers` and then a new file inside called `register.provider.ts`.

here is the code of the file:

```jsx
import {Schema} from "zod";

export function registerProvider<T>(
    name: string,
    options: {active: boolean},
    validation: Schema<T>,
    run: (libName: string, stars: number, values: T) => Promise<void>
) {
    // if not active, we can just pass an empty function, nothing will run
    if (!options.active) {
        return () => {};
    }

    // will validate and remove unnecessary values (Security wise)
    const env = validation.parse(process.env);

    // return the function we will run at the end of the job
    return async (libName: string, stars: number) => {
        console.log(`Running provider ${name}`);
        await run(libName, stars, env as T);
        console.log(`Finished running provider ${name}`);
    }
}
```

It’s not a lot of code, but it might be a little bit complex.

We are starting by creating a new function called `registerProvider`. That function gets a generic type `T`, which is basically our required environment variables.

Then we have 4 more parameters:

- name - that can be any of `Twilio`, `Discord`, `Slack`, or `Resend`.
- options - currently, one parameter is the provider active or not?
- validation - here, we pass the `zod` schema of our required parameters in our .env file.
- run - That actually functions to send the notifications. Pay attention that the parameters that are being passed into it are the library name, the number of stars, and the environment variables that we specified in `validation`

**Then we have the actual function:** 

First, we check if the provider is active or not. If not, we send an empty function.

Then, we validate and extract the variables we specify in our schema. If the variables are missing `zod` will send an error and will not let the application run.

Lastly, we return a function that gets the library name and the number of stars and triggers the notification.

Inside our `providers` folder, create a new file called `providers.ts` and add the following code inside:

```jsx
export const Providers = [];
```

Later, we will add all our providers there.

---

## Modify TriggerDev jobs

This article is a continuation of the previous article on creating a [GitHub stars monitor](https://dev.to/triggerdotdev/take-nextjs-to-the-next-level-create-a-github-stars-monitor-130a).

Edit the file `jobs/sync.stars.ts` and add the following code to the bottom of the file:

```jsx
const triggerNotification = client.defineJob({
    id: "trigger-notification",
    name: "Trigger Notification",
    version: "0.0.1",
    trigger: invokeTrigger({
      schema: z.object({
        stars: z.number(),
        library: z.string(),
        providerNumber: z.number(),
      })
    }),
    run: async (payload, io, ctx) => {
      await io.runTask("trigger-notification", async () => {
        return Providers[payload.providerNumber](payload.library, payload.stars);
      });
    }
});
```

This job gets the number of stars, library name, and provider number and triggers the notification for a specific provider from the previously defined providers.

Now, let’s go ahead and modify `getStars` at the end of the function add the following code:

```jsx
for (let i = 0; i < Providers.length; i++) {
      await triggerNotification.invoke(payload.name + '-' + i, {
          library: payload.name,
          stars: stargazers_count - payload.previousStarCount,
          providerNumber: i,
      });
  }
```

This will trigger a notification for every library.

The full page code:

```jsx
import { cronTrigger, invokeTrigger } from "@trigger.dev/sdk";
import { client } from "@/trigger";
import { prisma } from "../../helper/prisma";
import axios from "axios";
import { z } from "zod";
import {Providers} from "@/providers/providers";

// Your first job
// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline.
client.defineJob({
  id: "sync-stars",
  name: "Sync Stars Daily",
  version: "0.0.1",
  // Run a cron every day at 23:00 AM
  trigger: cronTrigger({
    cron: "0 23 * * *",
  }),
  run: async (payload, io, ctx) => {
    const repos = await io.runTask("get-stars", async () => {
      // get all libraries and current amount of stars
      return await prisma.repository.groupBy({
        by: ["name"],
        _sum: {
          stars: true,
        },
      });
    });

    //loop through all repos and invoke the Job that gets the latest stars
    for (const repo of repos) {
      await getStars.invoke(repo.name, {
        name: repo.name,
        previousStarCount: repo?._sum?.stars || 0,
      });
    }
  },
});

const getStars = client.defineJob({
  id: "get-latest-stars",
  name: "Get latest stars",
  version: "0.0.1",
  // Run a cron every day at 23:00 AM
  trigger: invokeTrigger({
    schema: z.object({
      name: z.string(),
      previousStarCount: z.number(),
    }),
  }),
  run: async (payload, io, ctx) => {
    const stargazers_count = await io.runTask("get-stars", async () => {
      const {data} = await axios.get(`https://api.github.com/repos/${payload.name}`, {
        headers: {
          authorization: `token ${process.env.TOKEN}`,
        },
      });
      return data.stargazers_count as number;
    });

    await io.runTask("upsert-stars", async () => {
      await prisma.repository.upsert({
        where: {
          name_day_month_year: {
            name: payload.name, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(),
          },
        }, update: {
          stars: stargazers_count - payload.previousStarCount,
        }, create: {
          name: payload.name, stars: stargazers_count - payload.previousStarCount, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(),
        },
      });
    });

    for (let i = 0; i < Providers.length; i++) {
        await triggerNotification.invoke(payload.name + '-' + i, {
            library: payload.name,
            stars: stargazers_count - payload.previousStarCount,
            providerNumber: i,
        });
    }
  },
});

const triggerNotification = client.defineJob({
    id: "trigger-notification",
    name: "Trigger Notification",
    version: "0.0.1",
    trigger: invokeTrigger({
      schema: z.object({
        stars: z.number(),
        library: z.string(),
        providerNumber: z.number(),
      })
    }),
    run: async (payload, io, ctx) => {
      await io.runTask("trigger-notification", async () => {
        return Providers[payload.providerNumber](payload.library, payload.stars);
      });
    }
});
```

Now, the fun part 🎉

Let’s go ahead and create our providers!

First create a new folder called `providers/lists`

---

## 1. Discord


![Discord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqw7u3s19vtffxc197up.png)


Create a new file called `discord.provider.ts` and add the following code:

```jsx
import {object, string} from "zod";
import {registerProvider} from "@/providers/register.provider";
import axios from "axios";

export const DiscordProvider = registerProvider(
    "discord",
    {active: true},
    object({
        DISCORD_WEBHOOK_URL: string(),
    }),
    async (libName, stars, values) => {
        await axios.post(values.DISCORD_WEBHOOK_URL, {content: `The library ${libName} has ${stars} new stars!`});
    }
);
```

As you can see, we are using the `registerProvider` to create a new provider called DiscordProvider

- We set the name to `discord`
- We set it to be active
- We specify that we need an environment variable called `DISCORD_WEBHOOK_URL`.
- We use a simple post command with Axios to add the information to the check.

To get `DISCORD_WEBHOOK_URL`:

1. Go to your Discord server
2. Click edit on one of the channels
3. Go to `Integrations` 
4. Click `Create Webhook`
5. Click on the created webhook, then click `Copy webhook URL`

Edit our `.env` file on our root project and add

```bash
SLACK_WEBHOOK_URL=<your copied url>
```


![Spidy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oyxvihf75afjubopy6dp.png)



---

## 2. Slack

![Slack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9t9ep538nt39j0xylcqp.png)

Create a new file called `slack.provider.ts` and add the following code:

```jsx
import {object, string} from "zod";
import {registerProvider} from "@/providers/register.provider";
import axios from "axios";

export const SlackProvider = registerProvider(
    "slack",
    {active: true},
    object({
        SLACK_WEBHOOK_URL: string(),
    }),
    async (libName, stars, values) => {
        await axios.post(values.SLACK_WEBHOOK_URL, {text: `The library ${libName} has ${stars} new stars!`});
    }
);
```

As you can see, we are using the `registerProvider` to create a new provider called SlackProvider

- We set the name to `slack`
- We set it to be active
- We specify that we need an environment variable called `SLACK_WEBHOOK_URL`.
- We use a simple post command with Axios to add the information to the check.

To get `SLACK_WEBHOOK_URL`:

1. Create a new Slack app by using this URL: https://api.slack.com/apps?new_app=1
2. Select the first option: “From scratch”
3. Give an app name (any) and Slack the workspace you would like to add the notifications too. Click `Create App`.
4. In “Add features and functionalities,” click `Incoming hook`
5. In Activate Incoming Webhooks, change it to “On”.
6. Click on “Add New Webhook to Workspace”.
7. Select the channel you want and click “Allow”.
8. Copy the webhook URL.

Edit our `.env` file on our root project and add

```bash
SLACK_WEBHOOK_URL=<your copied url>
```

![SlackBot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stlaf1xmprg629tjz7wv.png)

---

## 3. Email

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

You can use different kinds of email providers. For example, we will use **Resend** to send emails.

For that, let’s install resend on our project:

```bash
npm install resend --save
```

Create a new file called `resend.provider.ts` and add the following code:

```jsx
import {object, string} from "zod";
import {registerProvider} from "@/providers/register.provider";
import axios from "axios";
import { Resend } from 'resend';

export const ResendProvider = registerProvider(
    "resend",
    {active: true},
    object({
        RESEND_API_KEY: string(),
    }),
    async (libName, stars, values) => {
        const resend = new Resend(values.RESEND_API_KEY);
        await resend.emails.send({
            from: "Eric Allam <eric@trigger.dev>",
            to: ['eric@trigger.dev'],
            subject: 'New GitHub stars',
            html: `The library ${libName} has ${stars} new stars!`,
        });
    }
);
```

As you can see, we are using the `registerProvider` to create a new provider called ResendProvider

- We set the name to `resend`
- We set it to be active
- We specify that we need an environment variable called `RESEND_API_KEY`.
- We use the Resend library to send an email to ourselves with the new number of stars.

To get `RESEND_API_KEY`:

1. Create a new account at: https://resend.com
2. Go to `API Keys` or use this URL https://resend.com/api-keys
3. Click “+ Create API Key,” add the Key name, choose “Sending access” and use the default “All Domains”. Click Add.
4. Copy the API Key.

Edit our `.env` file on our root project and add

```bash
RESEND_API_KEY=<your API key>
```

![Eric Allam](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhk2hd2f53yfojn96yf3.png)

---

## 4. SMS

![Twilio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/036fdgpt0mp5h7wrisrn.png)

SMS are a little bit more complex as they require multiple variables.

For that, let’s install Twilio on our project:

```bash
npm install twilio --save
```

Create a new file called `twilio.provider.ts` and add the following code:

```jsx
import {object, string} from "zod";
import {registerProvider} from "@/providers/register.provider";
import axios from "axios";
import client from 'twilio';

export const TwilioProvider = registerProvider(
    "twilio",
    {active: true},
    object({
        TWILIO_SID: string(),
        TWILIO_AUTH_TOKEN: string(),
        TWILIO_FROM_NUMBER: string(),
        TWILIO_TO_NUMBER: string(),
    }),
    async (libName, stars, values) => {
        const twilio = client(values.TWILIO_SID, values.TWILIO_AUTH_TOKEN);
        await twilio.messages.create({
            body: `The library ${libName} has ${stars} new stars!`,
            from: values.TWILIO_FROM_NUMBER,
            to: values.TWILIO_TO_NUMBER,
        });
    }
);
```

As you can see, we are using the `registerProvider` to create a new provider called TwilioProvider

- We set the name to `twilio`
- We set it to be active
- We specify that we need environment variables: `TWILIO_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_FROM_NUMBER` and `TWILIO_TO_NUMBER`
- We use the Twilio `create` function to send an SMS.

To get `TWILIO_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_FROM_NUMBER` and `TWILIO_TO_NUMBER`

1. Create a new account at https://twilio.com
2. Mark that you want to use it to send SMSs.
3. Click “Get a phone number”
4. Copy the “Account SID”, “Auth Token” and “My Twilio Phone Number”

Edit our `.env` file on our root project and add

```bash
TWILIO_SID=<your SID key>
TWILIO_AUTH_TOKEN=<your AUTH TOKEN key>
TWILIO_FROM_NUMBER=<your FROM number>
TWILIO_TO_NUMBER=<your TO number>
```


![TwilioSMSs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/474q2p4ejvji18xuo9om.png)


---

## Create new providers

As you can see, now it’s super easy to create providers.

You can also use the open-source community to create new providers since they only need to create one new file inside the `providers/list` directory.

The last thing to do is edit your `providers.ts` file and add all your providers.

```jsx
import {DiscordProvider} from "@/providers/list/discord.provider";
import {ResendProvider} from "@/providers/list/resend.provider";
import {SlackProvider} from "@/providers/list/slack.provider";
import {TwilioProvider} from "@/providers/list/twilio.provider";

export const Providers = [
    DiscordProvider,
    ResendProvider,
    SlackProvider,
    TwilioProvider,
];
```

Feel free to create more providers for push notifications, web push notifications, in-app notifications, etc.

And you are done 🥳

---

## Let's connect! 🔌

As an open-source developer, you're invited to join our [community](https://discord.gg/nkqV9xBYWy) to contribute and engage with maintainers. Don't hesitate to visit our [GitHub repository](https://github.com/triggerdotdev/trigger.dev) to contribute and create issues related to Trigger.dev.

The source for this tutorial is available here:

[https://github.com/triggerdotdev/blog/tree/main/stars-monitor-notifications](https://github.com/triggerdotdev/blog/tree/main/stars-monitor-notifications)

Thank you for reading!