개요
GitHub Actions를 사용하여 CI/CD를 구축하면, 빌드, 테스트가 성공 또는 실패했을 때 Slack으로 메시지를 보내고 싶을 때가 있습니다. 이번 블로그 포스트에서는 GitHub Actions를 사용하여 Slack 메시지를 보내는 방법에 대해 알아보겠습니다.
Slack 앱 만들기
Slack은 대화형 메시징 플랫폼임으로, 메시지를 받는다는 의미는 누군가가 메시지를 보냈다는 것을 의미합니다. 따라서 GitHub Actions를 사용하여 Slack 메시지를 보내기 위해서는 Slack 메시지를 보내는 Slack 앱을 만들어야 합니다.
우선 다음 링크를 클릭하여 Slack API에 접속합니다.
그럼 다음과 같은 화면을 확인할 수 있습니다.

오른쪽 상단에 있는 Your apps를 선택하여 앱 생성 화면으로 이동합니다.

Create New App을 선택하여 새로운 앱을 생성합니다.

이때 From scratch를 선택하여 새로운 앱을 생성합니다.

그런 다음 앱의 이름을 입력하고, 이 앱을 사용할 Slack의 Workspace를 선택하고 Create App 버튼을 클릭합니다.

그럼 위와 같이 앱이 잘 생성되는 것을 확인할 수 있습니다.
이제 이 앱에 메시지를 보낼 수 있는 권한을 설정할 필요가 있습니다. 앱 목록 화면에서 새로 만든 앱의 이름을 선택하여 앱 상세 페이지로 이동합니다.

그런 다음 왼쪽 메뉴에서 OAuth & Permissions 메뉴를 선택하여 OAuth & Permissions 화면으로 이동합니다.

조금 스크롤하여 Scopes 섹션에서 Add an OAuth Scope 버튼을 클릭한 후 chat:write 권한을 추가합니다.
마지막으로 OAuth Tokens 섹션으로 이동한 후, Install to (workspace) 버튼을 클릭하여 Slack 앱을 설치합니다.

앱을 설치하고 나면 다음과 같이 Bot User OAuth Access Token이 생성되는 것을 확인할 수 있습니다.

이제 이렇게 만든 토큰을 GitHub Actions에서 사용하기 위해 GitHub의 Secrets에 저장해야 합니다. Slack 메시지를 발송하고 싶은 GitHub 저장소(Repository)로 이동합니다.
그 다음 Settings > Secrets and variables > Actions 메뉴로 클릭하여 Actions secrets and variables 페이지로 이동합니다.

화면에 표시된 Repository secrets의 New repository secret 버튼을 클릭하고, Name에 SLACK_BOT_TOKEN을 입력하고, Value에 Slack API 사이트에서 만든 토큰을 입력한 후 Add secret 버튼을 클릭하여 저장합니다.
채널 ID
GitHub Actions를 사용하여 Slack의 채널에 메시지를 보내기 위해서는 CHANNEL_ID가 필요합니다.
CHANNEL_ID는 Slack에서 습득할 수 있습니다. 개인에게 직접 보내고 싶은 경우, 다음과 같이 개인 프로필에서 Copy member ID를 클릭하여 CHANNEL_ID를 얻을 수 있습니다.

만약, 특정 채널에 메시지를 보내고 싶다면, Open channel details를 클릭하여 채널 상세 정보로 이동한 후,

하단에 표시된 Channel ID를 복사하여 사용하면 됩니다.
slack-github-action 사용하기
Slack에서 공식으로 제공하는 slack-github-action을 사용하면 GitHub Actions에서 Slack으로 메시지를 보낼 수 있습니다.
- slack-github-action: https://github.com/slackapi/slack-github-action
GitHub Actions에 다음과 같이 slack-github-action를 사용하도록 수정하면 Slack으로 메시지를 보낼 수 있습니다.
- 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 }}
간단한 메시지를 발송할 때는 이 slack-github-action을 사용하면 편리하게 Slack 메시지를 보낼 수 있습니다.
Composite Action
회사에서는 slack-github-action을 사용하지 않고, 다음과 같이 Composite Action을 사용하여 Slack 메시지를 보내는 방법을 사용하고 있습니다.
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 }}
Composite Action을 사용하면 GitHub Actions에서 Slack 메시지를 보내는 부분을 공통화하여 사용할 수 있습니다. Composite Action에 대해서는 다음 링크를 참고해 주시기 바랍니다.
Reviewer 알림
이렇게 만든 Composite Action을 사용하여 GitHub Actions를 사용하여 Reviewer에 지정된 경우 Slack 메시지를 보내는 방법에 대해 알아보겠습니다.
GitHub Actions에서는 Reviewer에 지정된 경우, Slack의 개인 채널로 메시지를 보내기 위해 다음과 같이 GitHub Actions를 작성할 수 있습니다.
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 }}
이 GitHub Actions를 좀 더 자세히 살펴보도록 하겠습니다.
name: '[Slack] Reviewer assigned'
on:
pull_request:
types: [review_requested]
...
이 GitHub Actions는 pull_request 이벤트의 review_requested를 사용하여 PR에 Reviewer가 지정된 경우에만 실행됩니다.
...
steps:
- name: Checkout Repository
uses: actions/checkout@v4
...
Composite Action는 Git에서 관리되기 때문에, 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);
...
actions/github-script@v7를 사용하여 Reviewer에게 메시지를 보내기 위한 메시지를 만듭니다. PR의 제목, 링크를 가져와서 Reviewer용 메시지를 만듭니다.
Reviewer notification
You are assigned to new PR.
- title: PR title
- link: https://...
이 메시지는 PR에 지정된 Reviewers 모두에게 메시지를 보내게 됩니다.
...
const reviewerLogins = reviewers.users.map(user => user.login);
for (const userName of reviewerLogins) {
...
이렇게 만든 메시지를 JSON을 그대로 전달하면 문제가 발생합니다. 그래서 Buffer를 사용하여 base64로 인코딩하여 core.setOutput을 사용하여 MESSAGES에 저장해서 전달하도록 했습니다.
...
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
마지막은 이전에 만든 Composite Action을 사용하여 Slack 메시지를 보냈습니다.
...
- 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 }}
...
매일 아침 메시지 발송
리뷰어가 Slack 메시지를 받은 후, PR을 리뷰하는 경우도 있지만, 바빠서 PR을 리뷰하지 못하는 경우도 있습니다. 이런 경우, 매일 아침에 리뷰어에게 메시지를 보내어 PR을 리뷰하도록 유도할 수 있습니다.
다음과 같이 GitHub Actions를 만들면, 평일 아침(월~금) 9시 30분(일본 시간 기준)에 리뷰어에게 메시지를 보낼 수 있습니다.
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 }}
이 GitHub Actions를 좀 더 자세히 살펴보도록 하겠습니다.
name: '[Slack] Every weekday at 9:30 AM'
on:
schedule:
- cron: '30 0 * * 1-5'
...
이 GitHub Actions는 평일(1-5) 9시 30분(일본 시간)에 실행됩니다.
...
steps:
- name: Checkout Repository
uses: actions/checkout@v4
...
Composite Action는 Git에서 관리되기 때문에, 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);
...
actions/github-script@v7를 사용하여 Reviewer에게 메시지를 보내기 위한 메시지를 만듭니다. 각각의 GitHub의 사용자가 할당된 모든 PR을 가져와서 리뷰어에게 보낼 메시지를 만듭니다.
Reviewer notification
You have assigned PRs. Please review when you have time.
- PR title1: https://...
- PR title2: https://...
- PR title3: https://...
- PR title4: https://...
이 메시지는 미리 작성한 GitHub 사용자 목록에 있는 모든 사용자에게 메시지를 보내게 됩니다.
...
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',
]
...
이렇게 만든 메시지를 JSON을 그대로 전달하면 문제가 발생합니다. 그래서 Buffer를 사용하여 base64로 인코딩하여 core.setOutput을 사용하여 MESSAGES에 저장해서 전달하도록 했습니다.
...
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
마지막은 이전에 만든 Composite Action을 사용하여 Slack 메시지를 보냈습니다.
...
- 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 }}
...
완료
이것으로 GitHub Actions를 사용하여 리뷰어에 지정된 경우 Slack 메시지를 보내는 방법과 매일 아침에 리뷰어에게 PR 목록을 Slack 메시지로 발송하는 방법에 대해서 알아보았습니다.
리뷰어에 지정된 것이 인식되지 않아서 리뷰가 계속 지연되는 문제가 있는 경우, 이러한 방법을 사용하여 리뷰어에게 메시지를 보내어 리뷰를 유도해 보시기 바랍니다.
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.














![[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통](https://img1c.coupangcdn.com/image/affiliate/banner/7cba8cb0601eebaf88a17a0c3cf65a63@2x.jpg)