왜 만들게 됐나
조직 개편 전에는 팀원들이 한 채널에 모여 있어서 누가 휴가인지, 어떤 회의실이 예약됐는지 슬쩍 보면 알 수 있었다.
개편 이후 팀이 분리되면서 연계 채널이 새로 생겼다. 그런데 서로 다른 팀 채널에 있다 보니 상대 팀의 휴가·회의실 정보를 알기가 불편해졌다. 매번 그룹웨어에 들어가서 확인하는 게 번거로웠다.
그래서 만들었다. 매일 아침 Slack으로 당일 휴가자와 회의실 예약 현황을 자동으로 보내주는 봇.
만들다 보니 기능이 붙었다. 출근 미등록자 DM 알림, 슬래시 커맨드로 날짜별 조회, 관리자 웹 페이지까지.
기술 스택
| 영역 | 기술 |
|---|---|
| Runtime | Node.js 20 |
| Framework | Express.js |
| 스케줄러 | node-cron |
| HTTP 클라이언트 | axios |
| 배포 | Docker Compose |
| 인프라 | OCI 서버 (홈서버에서 OCI로 이전) |
| 리버스 프록시 | Nginx |
| 데이터 저장 | JSON 파일 (DB 없음) |
DB 없이 JSON 파일로 상태를 관리한 이유는 단순하다. 저장해야 할 데이터가 “마지막으로 확인한 휴가/회의실 목록” 하나뿐이라 PostgreSQL까지 쓸 이유가 없었다.
전체 아키텍처
[그룹웨어 API]
│
│ SSO 인증 → 휴가 / 회의실 / 출근 데이터 수집
▼
[Node.js 봇 서버] ←→ [JSON 파일]
│ teams.json (팀·Webhook 설정)
│ members.json (출근 알림 대상)
│ settings.json (Slack 토큰, 크론 설정)
│ snapshot.json (마지막 상태 저장)
│
├─ 크론 스케줄 → Slack Webhook 알림
├─ 슬래시 커맨드 → Slack API 응답
└─ /admin 관리자 페이지
외부에서 /admin은 Tailscale VPN으로만 접근하고, 슬래시 커맨드 엔드포인트(/slack/command)만 Nginx를 통해 외부에 열어둔다.
전체 워크플로우

크게 세 가지 흐름으로 동작한다.
1. 서버 시작 시
snapshot.json로드 (재시작 후에도 이전 상태 복구)- 그룹웨어 SSO 로그인 → 세션 취득
- 마지막 스냅샷이 10분 초과됐으면 즉시 동기화 실행
2. 매 10분 정기 동기화 (sync 크론)
- 휴가·회의실 데이터를 API에서 새로 가져옴
- 이전 스냅샷과 비교해서 추가/취소된 건 감지
- 변동이 있으면 Slack Webhook으로 즉시 알림 발송
- 스냅샷 갱신 후
snapshot.json저장
3. 오전 9시 정기 발송
- 월요일: 이번 주 전체 휴가자 + 당일 회의실 예약
- 화~금: 당일 휴가자 + 당일 회의실 예약
- 출근 미등록자 → 개인 Slack DM 발송
알림 종류
휴가 변동 알림
휴가가 새로 등록되거나 취소되면 즉시 알림이 온다.

오전 정기 알림
매일 아침 9시에 당일 휴가자와 회의실 예약 현황을 한번에 보내준다.

출근 미등록 DM
출근 미등록자에게는 채널 알림 대신 개인 DM으로 조용히 보낸다.

슬래시 커맨드
Slack에서 직접 조회도 된다.

파일 구조
~/slackbot/
├── Dockerfile
├── docker-compose.yml
├── .env
├── server.js
├── package.json
└── data/ ← 볼륨 마운트 (재시작 시 유지)
├── teams.json (팀·Webhook 설정)
├── members.json (출근 알림 대상 멤버)
├── settings.json (Slack Bot Token, 크론 설정)
├── snapshot.json (마지막 상태 저장)
└── server.log
data/ 폴더를 볼륨으로 마운트해서 컨테이너를 재시작해도 설정과 상태가 유지된다.
크론 스케줄 전체 목록
| 크론 ID | 시간 | 내용 |
|---|---|---|
sync |
10분마다 | 휴가/회의실 변동 감지 → 알림 |
vacationWeekly |
월요일 09시 | 이번 주 전체 휴가자 |
vacationDaily |
화~금 09시 | 당일 휴가자 |
roomDaily |
월~금 09시 | 당일 회의실 예약 현황 |
commute |
월~금 09시 | 출근 미등록자 DM |
sessionKeep |
30분마다 | 세션 유지 ping |
logReset |
월요일 00시 | 로그 파일 초기화 |
모든 크론은 관리자 페이지에서 활성화/비활성화하고 스케줄 표현식도 수정할 수 있다.
다음 편에서는 그룹웨어 SSO 로그인 세션을 처리하는 방법을 다룬다. 쿠키 기반 SSO를 axios로 처리할 때 빠지기 쉬운 함정들을 정리한다.