The current most popular BirthdayBot on slack is used by more than 25,000 companies which makes birthday wishing business very lucrative, however it comes at a hefty price of $1.99 per user / month for premium package which can make it quite expensive for mid to large size companies.
In this tutorial we will use:
If you don't care about a step by step guide you can also just check out the final code.
Getting started
Notion
We will use notion for storing birthday dates of our slack members (as an alternative you can try out airtable or similar no-code tool).
- Create the new integration.
- Create the database page with
email
andbirthday
columns (email property type should betitle
and birthday should bedate
). - Share the database page with newly created integration.
Slack
Create a new slack app from an app manifest and install it to your workspace.
_metadata:
major_version: 1
minor_version: 1
display_information:
name: Birthday App
features:
bot_user:
display_name: birthdaybot
always_online: true
oauth_config:
scopes:
bot:
- chat:write
- chat:write.public
- users:read
- users:read.email
settings:
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
YAML-formatted configuration
Filtering the relevant parts
Get users from notion
getBirthdayUsers()
function fetches our users database and filters out the relevant users.
import * as R from "ramda";
import { isToday, parseISO } from "date-fns";
const notion = new Client({
auth: constants.NOTION_TOKEN,
});
async function getBirthdayUsers() {
const notionUsers = await notion.databases.query({
database_id: constants.NOTION_DATABASE_ID,
});
const birthdayUsers = R.filter(
(item) => isToday(parseISO(item.properties.birthday.date.start)),
notionUsers.results
);
return R.map((item) => {
return {
id: item.id,
email: item.properties.email.title[0].plain_text,
};
}, birthdayUsers);
}
Get users from slack
getSlackUsers()
function fetches all the slack members and filters out the bots. π€
async function getSlackUsers() {
const slackUsers = await slack.client.users.list();
const filteredSlackUsers = R.filter(
(item) => R.and(R.not(item.is_bot), R.not(item.name === "slackbot")),
slackUsers.members
);
return R.map((item) => {
return {
id: item.id,
email: item.profile.email,
};
}, filteredSlackUsers);
}
Random birthday wishes π
We don't want our messages to become boring too fast so we will create a JSON array of wishes we found somewhere online.
[
{
"text": "Count your life by smiles, not tears. Count your age by friends, not years. Happy birthday <@MENTION>!"
},
{
"text": "Happy birthday <@MENTION>! I hope all your birthday wishes and dreams come true."
}
]
Something to note here is <@MENTION>
keyword, which we replace with appropriate slack member id in order to make sure we send the message to the right user.
import messages from "./messages.json";
function getMessage(userId) {
const text = messages[Math.floor(Math.random() * messages.length)].text;
return R.replace("<@MENTION>", `<@${userId}>`, text);
}
Final steps
Sending the slack message
postMessage()
function posts a message to a channel. You could also try using blocks
argument to create visually rich and interactive messages. π¬
import { App } from "@slack/bolt";
const slack = new App({
signingSecret: constants.SLACK_SIGNING_SECRET,
token: constants.SLACK_BOT_TOKEN,
});
async function postMessage(text) {
return await slack.client.chat.postMessage({
text,
channel: constants.SLACK_CHANNEL_ID,
// blocks[] array
});
}
Putting it all together
Wishing our dear slack members a happy birthday.
async function main() {
const birthdayUsers = await getBirthdayUsers();
const slackUsers = await getSlackUsers();
R.forEach((user) => {
const slackUser = R.find(
(item) => R.equals(user.email, item.email),
slackUsers
);
if (R.not(R.isNil(slackUser))) {
postMessage(getMessage(slackUser.id));
}
}, birthdayUsers);
}
main();
Deployment
Easiest way to deploy this kind of bot would be to use Github Actions, since we can use their built-in cron job service.
name: Cron
on:
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
jobs:
cron:
runs-on: ubuntu-latest
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}
SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14"
- name: Install packages
run: npm install
- name: Start the app
run: npm run start
Check out github workflow for more details.
Using this configuration our bot will get triggered every day at 9 A.M. π
You can also run the workflow using the Actions tab on GitHub, GitHub CLI, or the REST API since we also added workflow_dispatch
event.
Conclusion
And there you have it, fully functional, customizable and zero-cost birthday bot for your workspace.
You are welcomed to upgrade it with additional features such as:
- giphy gifs for your messages.
-
isDisabled
flag on notion for users who don't want bots wishing them happy birthday. - Workplace anniversaries support.
Top comments (4)
Honestly, even though I love this kind of setup, maintenance is just too painful (the notion one where the database must be correctly filled-in).
After testing this I just found myself installing one of the slack bot on the market (Billy bot is free until 30 users). Worth considering
Thanks for the tutorial. I tested. But ended up with Billy birthday π Sorry!
After the integration, where should I add all this json code?
You can keep all the JSON code (birthday wishes) with the rest of the code or build a simple cloud storage with notion or airtable for adding/removing actions.
Both notion and airtable offer free to use REST api.