DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to build a slack birthday bot
Amer K
Amer K

Posted on

How to build a slack birthday bot

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).

  1. Create the new integration.
  2. Create the database page with email and birthday columns (email property type should be title and birthday should be date).
  3. 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
Enter fullscreen mode Exit fullscreen mode

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

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

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."
  }
]
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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 (2)

Collapse
 
akemimo1 profile image
AkemiMo

After the integration, where should I add all this json code?

Collapse
 
malcodeman profile image
Amer K

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.

βš‘οΈβ›“JavaScript Visualized: Scope

async await

☝️ Check out this all-time classic DEV post