프로젝트 진행 중 GitHub의 Issue와 PR을 Notion에서 한눈에 보고 싶었다. 노션의 기본 기능을 쓰려다가 유료화의 벽에 부딪히고, 결국 GitHub Actions를 이용해 무료로 자동화 시스템을 구축한 과정을 정리한다.
1. 문제 상황: "왜 안 되지?"
처음에는 노션의 /github 명령어를 사용해 동기화하려고 했다.
- Notion AI의 방해: /git만 쳐도 AI가 먼저 튀어나와서 명령어를 치기 힘들었다.
- 유료화의 벽: 막상 연동하려니 "GitHub 동기화 데이터베이스는 비즈니스 요금제 기능입니다"라는 메시지가 떴다. (팀 워크스페이스 제한)
- Gist 임베드 착각: 급한 마음에 Gist 입력창에 이슈 링크를 넣었으나 당연히 작동하지 않았다.
결국, 외부 툴 없이 GitHub Actions + Notion API를 사용해 직접 파이프라인을 구축하기로 결정했다.
2. 해결 방법: GitHub Actions 도입
구조 설계
- Trigger: 깃허브에서 Issue나 PR이 opened(생성)되면 실행.
- Logic: GitHub Actions(Javascript)가 Notion API를 호출.
- Destination: 이슈는 '이슈 DB'로, PR은 'PR DB'로 분기 처리하여 저장.
준비물
- Notion Integration: 내 통합 설정에서 새 통합(봇) 생성 → API Key 발급.
- Notion DB: 이슈용, PR용 데이터베이스 2개 생성 후 봇 연결(Connect) 필수.
- GitHub Secrets: 레포지토리 Settings에 API Key와 DB ID 등록.
3. 트러블 슈팅 (삽질 포인트 ⛏️)
구현 과정에서 겪은 주요 에러와 해결책이다.
이슈 1: Status 속성 불일치 (400 Error)
노션에는 '상태(Status)'라는 전용 속성이 있다. 하지만 API로 데이터를 보낼 때 단순히 Select(선택) 형식으로 보냈더니 타입이 맞지 않아 에러가 발생했다.
- 원인: 코드(select) != 노션 속성(status)
- 해결: 코드를 수정하는 것보다 노션 DB의 속성 유형을 '상태'에서 '선택(Select)'으로 변경하는 것이 훨씬 간단하고 확실했다.
이슈 2: 환경변수 이름 오타 (undefined)
분명 Secrets를 등록했는데 코드가 ID를 못 읽어왔다.
- 원인: GitHub Secrets에는 NOTION_DATABASE_ID로 저장하고, 코드에서는 NOTION_DB_ID로 호출함.
- 해결: 변수명을 하나로 통일하여 해결.
4. 최종 코드 (.github/workflows/main.yml)
이슈와 PR을 구분하여 서로 다른 데이터베이스에 저장하는 최종 스크립트다.
YAML
name: Sync Issues/PR to Notion
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
add-to-notion:
runs-on: ubuntu-latest
steps:
- name: Send to Notion
uses: actions/github-script@v6
env:
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
NOTION_ISSUE_ID: ${{ secrets.NOTION_DATABASE_ID }} # 이슈 DB ID
NOTION_PR_ID: ${{ secrets.NOTION_PR_DATABASE_ID }} # PR DB ID
with:
script: |
// 1. 이벤트 감지 (이슈 vs PR)
const isIssue = context.eventName === 'issues';
const payload = context.payload;
const item = isIssue ? payload.issue : payload.pull_request;
const title = `[${isIssue ? 'Issue' : 'PR'}] #${item.number} ${item.title}`;
const url = item.html_url;
// 2. 저장할 타겟 DB 설정
const targetDatabaseId = isIssue ? process.env.NOTION_ISSUE_ID : process.env.NOTION_PR_ID;
// 3. Notion API 호출
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NOTION_API_KEY}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify({
parent: { database_id: targetDatabaseId },
properties: {
"Name": {
title: [ { text: { content: title } } ]
},
"URL": {
url: url
},
"Status": {
select: { name: "Not Started" } // 노션에 'Not Started' 옵션(선택 속성)이 있어야 함
}
}
})
});
if (!response.ok) {
const errorText = await response.text();
core.setFailed(`Notion API Error: ${response.status} ${errorText}`);
} else {
console.log(`Successfully added to ${isIssue ? 'Issue' : 'PR'} Database!`);
}
5. 결론
- 비용 절감: 유료 플랜 업그레이드 없이 무료로 연동 성공.
- 커스터마이징: 노션의 동기화 DB는 수정이 제한적인데, 이 방식은 내가 원하는 대로 속성을 추가하거나 입맛대로 바꿀 수 있어 더 좋다.
- Git/Release 담당자의 삶: 이제 팀원들이 이슈를 만들면 자동으로 노션에 꽂힌다. 편안하다.
'Projects > Team Projects' 카테고리의 다른 글
| [Control Tower] 프론트엔드 혼자서 이틀 만에 백엔드 API 명세서 역설계하기 (feat. AI) (0) | 2026.01.22 |
|---|---|
| [Control Tower] Git 충돌 해결부터 사이드바 권한 분리까지 (0) | 2026.01.21 |
| [Control Tower] Cursor와 Figma를 활용한 효율적인 UI/UX 프로토타이핑 (0) | 2026.01.13 |
| [Control Tower] Day 1-2: 팀 빌딩, 항공사 HR SaaS 기획, 그리고 협업 환경 구축기 (0) | 2026.01.07 |
| 세미프로젝트(2025.10.23 ~ 11.20) 회고록 (0) | 2025.11.21 |