🚀 Top 4 ways to send notifications about new stars ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

Eric Allam - Nov 21 '23 - - Dev Community

TL;DR

In the last article, I discussed creating a GitHub stars monitor.
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


Your background job platform 🔌

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!

 

GiveUsStars

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

Star the Trigger.dev repository ⭐️


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:

npm install zod --save
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

export const Providers = [];
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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:

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

This will trigger a notification for every library.

The full page code:

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

Now, the fun part 🎉

Let’s go ahead and create our providers!

First create a new folder called providers/lists


1. Discord

Discord

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

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

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

SLACK_WEBHOOK_URL=<your copied url>
Enter fullscreen mode Exit fullscreen mode

Spidy


2. Slack

Slack

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

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

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

SLACK_WEBHOOK_URL=<your copied url>
Enter fullscreen mode Exit fullscreen mode

SlackBot


3. Email

Email

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:

npm install resend --save
Enter fullscreen mode Exit fullscreen mode

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

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

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

RESEND_API_KEY=<your API key>
Enter fullscreen mode Exit fullscreen mode

Eric Allam


4. SMS

Twilio

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

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

npm install twilio --save
Enter fullscreen mode Exit fullscreen mode

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

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

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

TWILIO_SID=<your SID key>
TWILIO_AUTH_TOKEN=<your AUTH TOKEN key>
TWILIO_FROM_NUMBER=<your FROM number>
TWILIO_TO_NUMBER=<your TO number>
Enter fullscreen mode Exit fullscreen mode

TwilioSMSs


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.

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

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 to contribute and engage with maintainers. Don't hesitate to visit our GitHub repository 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

Thank you for reading!

. . . . . . . . . . . . . . .
Terabox Video Player