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:
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:
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
, 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:
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.
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);
});
}
});
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,
});
}
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);
});
}
});
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:
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
:
- 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
SLACK_WEBHOOK_URL=<your copied url>
2. 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!`});
}
);
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
SLACK_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:
npm install resend --save
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!`,
});
}
);
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
RESEND_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:
npm install twilio --save
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,
});
}
);
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
TWILIO_SID=<your SID key>
TWILIO_AUTH_TOKEN=<your AUTH TOKEN key>
TWILIO_FROM_NUMBER=<your FROM number>
TWILIO_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.
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 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!
Top comments (9)
This is an interesting idea. When a user stars a repo, what do you think it signifies? If a repo has lots of stars what do you interpret that to mean? Do you think it's quality of code or do you think it's more about how common the problem is for other GitHub users?
I'm curious because I started my own project using GitHub stars as well, and I'm trying to understand more about why users star repos and how they use them afterwards :)
Copilot for your GitHub stars
Dave Parr ・ Nov 19
Hey @daveparr, I know why this matters as someone with an OSS Project with 3.3K Stars.
It's more like an outreach for you. The people who star your repo, bookmark it. They are notified of the releases, discussions, issues, and more. And this comes into their GitHub feed. Also, if someone uses a filter to search for specific topics like
resume
machine-learning
, orsearch engine
it filters by the stars. The more the stars, the more you rank on top.That's all. Like why anyone should subscribe on YouTube and press the bell icon. It's the same with GH Stars; they matter, but to an extent, that's all.
Great banner! :)
Great article, I'm starting to use Trigger.dev quite a bit and I love the ease of use.
I love how TriggerDev handling background jobs!
This is an interesting article, never thought of using to create something like github star monitor. I may create one for Github forks.
Erik (@maverickdotdev) never fails to surprise us with awesome & practical use cases for Trigger.dev.
Just wanted to say that this was the best written guide I've seen in a long time.
Good job, thank you!