How to build a slack birthday bot
Tags: javascript, opensource, slack, bot
Published: 2022-01-20 14:43:37

The current most popular [BirthdayBot](https://birthdaybot.io) 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:
* [bolt](https://github.com/slackapi/bolt-js)
* [notion](https://github.com/makenotion/notion-sdk-js)
* [ramda](https://github.com/ramda/ramda)
* [date-fns](https://github.com/date-fns/date-fns)

> If you don't care about a step by step guide you can also just [check out the final code](https://github.com/malcodeman/birthday-bot).

## 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](https://www.notion.so/my-integrations).
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](https://api.slack.com/apps?new_app=1) from an app manifest and install it to your workspace.

```javascript
_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.

```javascript
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. 🤖

```javascript
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.
```javascript
[
  {
    "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.

```javascript
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. 💬

```javascript
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.

```javascript
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.
```javascript
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](https://github.com/malcodeman/birthday-bot/blob/main/.github/workflows/cron.yml) 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](https://github.com/Giphy/giphy-js) gifs for your messages.
* `isDisabled` flag on notion for users who don't want bots wishing them happy birthday.
* Workplace anniversaries support.