Contents
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.
Click the Your apps
button on the top right to go to the app creation screen.
Click the Create New App
button to create a new app.
At this time, select From scratch
to create a new app.
Enter the name of the app, select the Slack Workspace to use this app, and click the Create App
button.
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.
Then, select the OAuth & Permissions
menu from the left menu to go to the OAuth & Permissions
screen.
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.
After installing the app, you will see that the Bot User OAuth Access Token
is created as shown above.
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.
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.
If you want to send a message to a specific channel, click Open channel details
to go to the channel details,
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
.
- slack-github-action: https://github.com/slackapi/slack-github-action
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, '<') // < → <
.replace(/>/g, '>') // > → >
.replace(/"/g, '"') // " → "
.replace(/'/g, '''); // ' → '
}
// 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, '&') // & → &
.replace(/</g, '<') // < → <
.replace(/>/g, '>') // > → >
.replace(/"/g, '"') // " → "
.replace(/'/g, '''); // ' → '
}
// 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
Deku
.Deku
created the applications with Flutter.If you have interested, please try to download them for free.