[GitHub Actions] Send Slack message for Reviewer

2024-12-17 hit count image

Let's see how to send Slack messages when Reviewer is assigned using GitHub Actions and how to send PR list to Reviewer as Slack message every morning.

Outline

When using GitHub Actions to build CI/CD, sometimes you want to send messages to Slack when the build and test are successful or failed. In this blog post, I will introduce how to send Slack messages using GitHub Actions.

Create Slack App

Slack is an interactive messaging platform, so receiving messages means that someone has sent a message. Therefore, to send Slack messages using GitHub Actions, you need to create a Slack app that sends messages.

First, click the following link to access the Slack API.

Then, you will see the following screen.

GitHub Actions Send Slack message - Slack API site

Click the Your apps button on the top right to go to the app creation screen.

GitHub Actions Send Slack message - Create new Slack app

Click the Create New App button to create a new app.

GitHub Actions Send Slack message - Create new Slack app options

At this time, select From scratch to create a new app.

GitHub Actions Send Slack message - Enter app name and workspace

Enter the name of the app, select the Slack Workspace to use this app, and click the Create App button.

GitHub Actions Send Slack message - App created

Then, you can see that the app is created successfully as shown above.

Now, you need to set the permission to send messages to this app. Select the name of the newly created app on the app list screen to go to the app details page.

GitHub Actions Send Slack message - OAuth and Permissions

Then, select the OAuth & Permissions menu from the left menu to go to the OAuth & Permissions screen.

GitHub Actions Send Slack message - OAuth and Permissions scopes

Scroll down a bit and click the Add an OAuth Scope button in the Scopes section, then add the chat:write permission.

Lastly, go to the OAuth Tokens section and click the Install to (workspace) button to install the Slack app.

GitHub Actions Send Slack message - OAuth and Permissions install app

After installing the app, you will see that the Bot User OAuth Access Token is created as shown above.

GitHub Actions Send Slack message - OAuth and Permissions Bot User OAuth Access Token

Now, you need to save this Bot User OAuth Access Token to use it in GitHub Actions. Go to the GitHub repository where you want to send Slack messages.

Then, click the Settings > Secrets and variables > Actions menu to go to the Actions secrets and variables page.

GitHub Actions Send Slack message - GitHub secrets and variables

Click the New repository secret button on the Repository secrets screen, enter Name as SLACK_BOT_TOKEN and enter the token created on the Slack API site in Value, and click the Add secret button to save it.

Channel ID

To send messages to a Slack channel using GitHub Actions, you need a CHANNEL_ID.

You can get the CHANNEL_ID from Slack. If you want to send it directly to an individual, you can get the CHANNEL_ID by clicking Copy member ID in the individual profile.

GitHub Actions Send Slack message - Copy Slack member channel ID

If you want to send a message to a specific channel, click Open channel details to go to the channel details,

GitHub Actions Send Slack message - Copy Slack channel ID

and copy the Channel ID displayed at the bottom.

Use slack-github-action

You can send messages to Slack using the slack-github-action officially provided by Slack.

You can send messages to Slack by modifying GitHub Actions as follows to use slack-github-action.


- name: Post to a Slack channel
  id: slack
  uses: slackapi/[email protected]
  with:
    channel-id: 'CHANNEL_ID'
    payload: |
      {
        "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
            }
          }
        ]
      }
  env:
    SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

When sending a simple message, you can conveniently send a Slack message by using this slack-github-action.

You can send a message easily using the slack-github-action provided by Slack when you want to send a simple message.

Composite Action

The company I work for does not use slack-github-action, but uses the following code to send Slack messages by using Composite Action.


name: 'Send Slack messages'
description: 'Send Slack messages'

inputs:
  GITHUB_TOKEN:
    description: 'GitHub token to use GitHub API'
    required: true
  SLACK_BOT_TOKEN:
    description: 'Token for Slack bot'
    required: true
  MESSAGES:
    description: '(JSON) Multiple users and multiple Slack messages'
    required: true

runs:
  using: 'composite'
  steps:
    - name: Send Slack messages
      uses: actions/github-script@v7
      with:
        github-token: ${{ inputs.GITHUB_TOKEN }}
        script: |
          const slackToken = process.env.SLACK_TOKEN
          const messages = JSON.parse(Buffer.from(process.env.MESSAGES, 'base64').toString('utf-8'));
          const channelIDs = {
            'GITHUB_USER_NAME_1': 'USER_CHANNEL_ID_1',
            'GITHUB_USER_NAME_2': 'USER_CHANNEL_ID_2',
            'GITHUB_USER_NAME_2': 'USER_CHANNEL_ID_2',
            'GITHUB_USER_NAME_3': 'USER_CHANNEL_ID_3',
            'GITHUB_USER_NAME_3': 'USER_CHANNEL_ID_3',
            'GITHUB_USER_NAME_4': 'USER_CHANNEL_ID_4',
          }

          for (const message of messages) {
            const { userName, messages: blocks } = message
            const channel = channelIDs[userName]
            fetch('https://slack.com/api/chat.postMessage', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${slackToken}`,
              },
              body: JSON.stringify({
                channel,
                blocks,
              })
            })
            .then(response => response.json())
            .then(data => {
              if (!data.ok) {
                throw new Error(`Slack API error: ${data.error}`);
              }
              console.log('Message sent to Slack successfully');
            })
            .catch(error => {
              console.error('Error sending message to Slack:', error);
            });
          }
      env:
        SLACK_TOKEN: ${{ inputs.SLACK_BOT_TOKEN }}
        MESSAGES: ${{ inputs.MESSAGES }}

By using Composite Action, you can make the part of sending Slack messages common and use it. For more information about Composite Action, please refer to the following link.

Reviewer Notification

Next, I will show you how to send a Slack message using GitHub Actions when a Reviewer is assigned by using the Composite Action created above.

You can create a GitHub Actions as follows to send a message to a private channel in Slack when a Reviewer is assigned in a PR.


name: '[Slack] Reviewer assigned'

on:
  pull_request:
    types: [review_requested]

jobs:
  notify_for_reviewer:
    runs-on: ubuntu-latest
    timeout-minutes: 1
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
      - name: Make Slack messages
        id: make-slack-messages
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const prNumber = context.payload.pull_request.number;
            const prTitle = context.payload.pull_request.title;
            const prLink = context.payload.pull_request.html_url;
            const { data: reviewers } = await github.rest.pulls.listRequestedReviewers({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: prNumber,
            });

            let slackMessages = []
            const reviewerLogins = reviewers.users.map(user => user.login);
            for (const userName of reviewerLogins) {
              const message = `*Reviewer notification*\n\nYou are assigned to new PR.\n\n- title: ${prTitle}\n- link: ${prLink}`
              slackMessages.push({
                userName,
                messages: [
                  {
                    type: 'section',
                    text: {
                      type: 'mrkdwn',
                      text: message,
                    }
                  }
                ]
              })
            }
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
      - name: Send Slack messages
        uses: ./.github/actions/send_slack_messages
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
          MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}

Let’s take a closer look at this GitHub Actions.


name: '[Slack] Reviewer assigned'

on:
  pull_request:
    types: [review_requested]
...

This GitHub Actions is triggered only when the review_requested event of the pull_request is used.


...
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
...

Since Composite Action is managed by Git, you need to check out the repository using actions/checkout@v4.


...
      - name: Make Slack messages
        id: make-slack-messages
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const prNumber = context.payload.pull_request.number;
            const prTitle = context.payload.pull_request.title;
            const prLink = context.payload.pull_request.html_url;
            const { data: reviewers } = await github.rest.pulls.listRequestedReviewers({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: prNumber,
            });

            let slackMessages = []
            const reviewerLogins = reviewers.users.map(user => user.login);
            for (const userName of reviewerLogins) {
              const message = `*Reviewer notification*\n\nYou are assigned to new PR.\n\n- title: ${prTitle}\n- link: ${prLink}`
              slackMessages.push({
                userName,
                messages: [
                  {
                    type: 'section',
                    text: {
                      type: 'mrkdwn',
                      text: message,
                    }
                  }
                ]
              })
            }
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
...

Using actions/github-script@v7, create a message to send to the Reviewer. Get the title and link of the PR and create a message to send to the Reviewer.

Reviewer notification
You are assigned to new PR.
- title: PR title
- link: https://...

This message will be sent to all Reviewers specified in the PR.


...
            const reviewerLogins = reviewers.users.map(user => user.login);
            for (const userName of reviewerLogins) {
...

If you send this message as it is in JSON to the Composite Action, a problem will occur. Therefore, use Buffer to encode it in base64 and save it in MESSAGES using core.setOutput to pass it on.


...
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
...

Lastly, send a Slack message using the previously created Composite Action.


...
      - name: Send Slack messages
        uses: ./.github/actions/send_slack_messages
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
          MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
...

Send messages every morning

When reviewers receive Slack messages, they sometimes review PRs after receiving Slack messages, but sometimes they are too busy to review PRs. In this case, you can send a message to the reviewer every morning to review the PR.

If you create a GitHub Actions as follows, you can send messages to reviewers every weekday morning (Monday to Friday) at 9:30 AM (Japan time).


name: '[Slack] Every weekday at 9:30 AM'

on:
  schedule:
    - cron: '30 0 * * 1-5'

jobs:
  notify_reviewers_every_weekday:
    runs-on: ubuntu-latest
    timeout-minutes: 1
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Make Slack messages
        id: make-slack-messages
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const userList = [
              'GITHUB_USER_NAME_1',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_4',
            ]
            const escapeForSlack = (text) => {
              return text
                .replace(/&/g, '&')   // & → &
                .replace(/</g, '&lt;')    // < → &lt;
                .replace(/>/g, '&gt;')    // > → &gt;
                .replace(/"/g, '&quot;')  // " → &quot;
                .replace(/'/g, '&#39;');  // ' → &#39;
            }

            // Get All PRs
            const prList = []
            let pageIndex = 1
            let hasMorePages = true
            while (hasMorePages) {
              const result = await github.rest.pulls.list({
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open',
                per_page: 100,
                page: pageIndex,
              })
              if (result.data.length > 0) {
                prList.push(...result.data)
                pageIndex += 1
              } else {
                hasMorePages = false
              }
            }
            let slackMessages = []
            for (const userName of userList) {
              const reviewerPRList = prList.filter(pr => pr.user.login !== 'dependabot[bot]' &&
                pr.requested_reviewers.some(reviewer => reviewer.login === userName)
              );
              if (reviewerPRList.length > 0) {
                let message = '*Reviewer notification*\n\nYou have assigned PRs. Please review when you have time.\n'
                reviewerPRList.forEach(pr => {
                  message += `\n- <${pr.html_url}|${escapeForSlack(pr.title)}>`;
                });
                slackMessages.push({
                  userName,
                  messages: [
                    {
                      type: 'section',
                      text: {
                        type: 'mrkdwn',
                        text: message,
                      }
                    }
                  ]
                })
              }
            }
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
      - name: Send Slack messages
        uses: ./.github/actions/send_slack_messages
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
          MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}

Let’s take a closer look at this GitHub Actions.


name: '[Slack] Every weekday at 9:30 AM'

on:
  schedule:
    - cron: '30 0 * * 1-5'
...

This GitHub Actions is triggered every weekday (1-5) at 9:30 AM (Japan time).


...
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
...

Since Composite Action is managed by Git, you need to check out the repository using actions/checkout@v4.


...
      - name: Make Slack messages
        id: make-slack-messages
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const userList = [
              'GITHUB_USER_NAME_1',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_4',
            ]
            const escapeForSlack = (text) => {
              return text
                .replace(/&/g, '&amp;')   // & → &amp;
                .replace(/</g, '&lt;')    // < → &lt;
                .replace(/>/g, '&gt;')    // > → &gt;
                .replace(/"/g, '&quot;')  // " → &quot;
                .replace(/'/g, '&#39;');  // ' → &#39;
            }

            // Get All PRs
            const prList = []
            let pageIndex = 1
            let hasMorePages = true
            while (hasMorePages) {
              const result = await github.rest.pulls.list({
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open',
                per_page: 100,
                page: pageIndex,
              })
              if (result.data.length > 0) {
                prList.push(...result.data)
                pageIndex += 1
              } else {
                hasMorePages = false
              }
            }
            let slackMessages = []
            for (const userName of userList) {
              const reviewerPRList = prList.filter(pr => pr.user.login !== 'dependabot[bot]' &&
                pr.requested_reviewers.some(reviewer => reviewer.login === userName)
              );
              if (reviewerPRList.length > 0) {
                let message = '*Reviewer notification*\n\nYou have assigned PRs. Please review when you have time.\n'
                reviewerPRList.forEach(pr => {
                  message += `\n- <${pr.html_url}|${escapeForSlack(pr.title)}>`;
                });
                slackMessages.push({
                  userName,
                  messages: [
                    {
                      type: 'section',
                      text: {
                        type: 'mrkdwn',
                        text: message,
                      }
                    }
                  ]
                })
              }
            }
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
...

Using actions/github-script@v7, create a message to send to the Reviewer. Get all PRs and create a message to send to the Reviewer.

Reviewer notification
You have assigned PRs. Please review when you have time.
- PR title1: https://...
- PR title2: https://...
- PR title3: https://...
- PR title4: https://...

This message will be sent to all reviewers specified in the GitHub user list.


...
            const userList = [
              'GITHUB_USER_NAME_1',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_2',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_3',
              'GITHUB_USER_NAME_4',
            ]
...

The error occurs when passing the message created like this to Composite Action by JSON. Therefore, you need to encode it in base64 using Buffer and save it in MESSAGES using core.setOutput to pass it on.

...
            const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
            core.setOutput('MESSAGES', encodedMessages);
...

Lastly, send a Slack message using the previously created Composite Action.


...
      - name: Send Slack messages
        uses: ./.github/actions/send_slack_messages
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
          MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
...

Completed

Done! We’ve seen how to send Slack messages using GitHub Actions when a Reviewer is assigned and how to send a PR list to Reviewers as a Slack message every morning.

If you have an issue that the review is delayed because the Reviewer assigned is not recognized, please try sending a message to the Reviewer to induce the review using this method.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.

Posts