Top 4 ways to send notifications about new GitHub stars
CTO, Trigger.dev
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.
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!
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:
_10npm 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:
_23import {Schema} from "zod";_23_23export function registerProvider<T>(_23 name: string,_23 options: {active: boolean},_23 validation: Schema<T>,_23 run: (libName: string, stars: number, values: T) => Promise<void>_23) {_23 // if not active, we can just pass an empty function, nothing will run_23 if (!options.active) {_23 return () => {};_23 }_23_23 // will validate and remove unnecessary values (Security wise)_23 const env = validation.parse(process.env);_23_23 // return the function we will run at the end of the job_23 return async (libName: string, stars: number) => {_23 console.log(`Running provider ${name}`);_23 await run(libName, stars, env as T);_23 console.log(`Finished running provider ${name}`);_23 }_23}
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
, orResend
. - 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:
_10export 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.
Edit the file jobs/sync.stars.ts
and add the following code to the bottom of the file:
_17const triggerNotification = client.defineJob({_17 id: "trigger-notification",_17 name: "Trigger Notification",_17 version: "0.0.1",_17 trigger: invokeTrigger({_17 schema: z.object({_17 stars: z.number(),_17 library: z.string(),_17 providerNumber: z.number(),_17 }),_17 }),_17 run: async (payload, io, ctx) => {_17 await io.runTask("trigger-notification", async () => {_17 return Providers[payload.providerNumber](payload.library, payload.stars);_17 });_17 },_17});
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:
_10for (let i = 0; i < Providers.length; i++) {_10 await triggerNotification.invoke(payload.name + "-" + i, {_10 library: payload.name,_10 stars: stargazers_count - payload.previousStarCount,_10 providerNumber: i,_10 });_10}
This will trigger a notification for every library.
The full page code:
_100import { cronTrigger, invokeTrigger } from "@trigger.dev/sdk";_100import { client } from "@/trigger";_100import { prisma } from "../../helper/prisma";_100import axios from "axios";_100import { z } from "zod";_100import {Providers} from "@/providers/providers";_100_100// Your first job_100// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline._100client.defineJob({_100 id: "sync-stars",_100 name: "Sync Stars Daily",_100 version: "0.0.1",_100 // Run a cron every day at 23:00 AM_100 trigger: cronTrigger({_100 cron: "0 23 * * *",_100 }),_100 run: async (payload, io, ctx) => {_100 const repos = await io.runTask("get-stars", async () => {_100 // get all libraries and current amount of stars_100 return await prisma.repository.groupBy({_100 by: ["name"],_100 _sum: {_100 stars: true,_100 },_100 });_100 });_100_100 //loop through all repos and invoke the Job that gets the latest stars_100 for (const repo of repos) {_100 await getStars.invoke(repo.name, {_100 name: repo.name,_100 previousStarCount: repo?._sum?.stars || 0,_100 });_100 }_100 },_100});_100_100const getStars = client.defineJob({_100 id: "get-latest-stars",_100 name: "Get latest stars",_100 version: "0.0.1",_100 // Run a cron every day at 23:00 AM_100 trigger: invokeTrigger({_100 schema: z.object({_100 name: z.string(),_100 previousStarCount: z.number(),_100 }),_100 }),_100 run: async (payload, io, ctx) => {_100 const stargazers_count = await io.runTask("get-stars", async () => {_100 const {data} = await axios.get(`https://api.github.com/repos/${payload.name}`, {_100 headers: {_100 authorization: `token ${process.env.TOKEN}`,_100 },_100 });_100 return data.stargazers_count as number;_100 });_100_100 await io.runTask("upsert-stars", async () => {_100 await prisma.repository.upsert({_100 where: {_100 name_day_month_year: {_100 name: payload.name, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(),_100 },_100 }, update: {_100 stars: stargazers_count - payload.previousStarCount,_100 }, create: {_100 name: payload.name, stars: stargazers_count - payload.previousStarCount, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(),_100 },_100 });_100 });_100_100 for (let i = 0; i < Providers.length; i++) {_100 await triggerNotification.invoke(payload.name + '-' + i, {_100 library: payload.name,_100 stars: stargazers_count - payload.previousStarCount,_100 providerNumber: i,_100 });_100 }_100 },_100});_100_100const triggerNotification = client.defineJob({_100 id: "trigger-notification",_100 name: "Trigger Notification",_100 version: "0.0.1",_100 trigger: invokeTrigger({_100 schema: z.object({_100 stars: z.number(),_100 library: z.string(),_100 providerNumber: z.number(),_100 })_100 }),_100 run: async (payload, io, ctx) => {_100 await io.runTask("trigger-notification", async () => {_100 return Providers[payload.providerNumber](payload.library, payload.stars);_100 });_100 }_100});
Now, the fun part 🎉
Let’s go ahead and create our providers!
First create a new folder called providers/lists
1. Discord
Create a new file called discord.provider.ts
and add the following code:
_16import { object, string } from "zod";_16import { registerProvider } from "@/providers/register.provider";_16import axios from "axios";_16_16export const DiscordProvider = registerProvider(_16 "discord",_16 { active: true },_16 object({_16 DISCORD_WEBHOOK_URL: string(),_16 }),_16 async (libName, stars, values) => {_16 await axios.post(values.DISCORD_WEBHOOK_URL, {_16 content: `The library ${libName} has ${stars} new stars!`,_16 });_16 }_16);
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
:
- Go to your Discord server
- Click edit on one of the channels
- Go to
Integrations
- Click
Create Webhook
- Click on the created webhook, then click
Copy webhook URL
Edit our .env
file on our root project and add
_10SLACK_WEBHOOK_URL=<your copied url>
2. Slack
Create a new file called slack.provider.ts
and add the following code:
_16import { object, string } from "zod";_16import { registerProvider } from "@/providers/register.provider";_16import axios from "axios";_16_16export const SlackProvider = registerProvider(_16 "slack",_16 { active: true },_16 object({_16 SLACK_WEBHOOK_URL: string(),_16 }),_16 async (libName, stars, values) => {_16 await axios.post(values.SLACK_WEBHOOK_URL, {_16 text: `The library ${libName} has ${stars} new stars!`,_16 });_16 }_16);
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
:
- Create a new Slack app by using this URL: https://api.slack.com/apps?new_app=1
- Select the first option: “From scratch”
- Give an app name (any) and Slack the workspace you would like to add the notifications too. Click
Create App
. - In “Add features and functionalities,” click
Incoming hook
- In Activate Incoming Webhooks, change it to “On”.
- Click on “Add New Webhook to Workspace”.
- Select the channel you want and click “Allow”.
- Copy the webhook URL.
Edit our .env
file on our root project and add
_10SLACK_WEBHOOK_URL=<your copied url>
3. 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:
_10npm install resend --save
Create a new file called resend.provider.ts
and add the following code:
_21import { object, string } from "zod";_21import { registerProvider } from "@/providers/register.provider";_21import axios from "axios";_21import { Resend } from "resend";_21_21export const ResendProvider = registerProvider(_21 "resend",_21 { active: true },_21 object({_21 RESEND_API_KEY: string(),_21 }),_21 async (libName, stars, values) => {_21 const resend = new Resend(values.RESEND_API_KEY);_21 await resend.emails.send({_21 from: "Eric Allam <[email protected]>",_21 to: ["[email protected]"],_21 subject: "New GitHub stars",_21 html: `The library ${libName} has ${stars} new stars!`,_21 });_21 }_21);
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
:
- Create a new account at: https://resend.com
- Go to
API Keys
or use this URL https://resend.com/api-keys - Click “+ Create API Key,” add the Key name, choose “Sending access” and use the default “All Domains”. Click Add.
- Copy the API Key.
Edit our .env
file on our root project and add
_10RESEND_API_KEY=<your API key>
4. SMS
SMS are a little bit more complex as they require multiple variables.
For that, let’s install Twilio on our project:
_10npm install twilio --save
Create a new file called twilio.provider.ts
and add the following code:
_23import { object, string } from "zod";_23import { registerProvider } from "@/providers/register.provider";_23import axios from "axios";_23import client from "twilio";_23_23export const TwilioProvider = registerProvider(_23 "twilio",_23 { active: true },_23 object({_23 TWILIO_SID: string(),_23 TWILIO_AUTH_TOKEN: string(),_23 TWILIO_FROM_NUMBER: string(),_23 TWILIO_TO_NUMBER: string(),_23 }),_23 async (libName, stars, values) => {_23 const twilio = client(values.TWILIO_SID, values.TWILIO_AUTH_TOKEN);_23 await twilio.messages.create({_23 body: `The library ${libName} has ${stars} new stars!`,_23 from: values.TWILIO_FROM_NUMBER,_23 to: values.TWILIO_TO_NUMBER,_23 });_23 }_23);
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
andTWILIO_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
- Create a new account at https://twilio.com
- Mark that you want to use it to send SMSs.
- Click “Get a phone number”
- Copy the “Account SID”, “Auth Token” and “My Twilio Phone Number”
Edit our .env
file on our root project and add
_10TWILIO_SID=<your SID key>_10TWILIO_AUTH_TOKEN=<your AUTH TOKEN key>_10TWILIO_FROM_NUMBER=<your FROM number>_10TWILIO_TO_NUMBER=<your TO number>
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.
_11import { DiscordProvider } from "@/providers/list/discord.provider";_11import { ResendProvider } from "@/providers/list/resend.provider";_11import { SlackProvider } from "@/providers/list/slack.provider";_11import { TwilioProvider } from "@/providers/list/twilio.provider";_11_11export const Providers = [_11 DiscordProvider,_11 ResendProvider,_11 SlackProvider,_11 TwilioProvider,_11];
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!