본 문서는 사내 AI PM 어시스턴트 Paimy의 시스템 아키텍처를 정의한다.
| 레이어 | 기술 | 용도 |
|---|---|---|
| Frontend | Slack | 사용자 인터페이스 (유일한 접점) |
| Backend | Vercel (Serverless) | API Routes, Cron Jobs, Core Logic |
| Database | Supabase (PostgreSQL) | 매핑 테이블, 설정, 컨텍스트 저장 |
| AI | Claude Sonnet 4.5 | 자연어 이해, Tool Use, 응답 생성 |
| Integration | MCP Servers | Notion, Google Calendar, Gmail 연동 |
┌─────────────────────────────────────────────────────────────────────┐
│ User Layer │
│ 👤 사내 사용자 (Slack) │
└─────────────────────────────────┬───────────────────────────────────┘
│ Slack Events & Messages
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Interface Layer │
│ 💬 Slack Workspace (@Paimy Bot) │
└─────────────────────────────────┬───────────────────────────────────┘
│ Webhook / Web API
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Application Layer (Vercel) │
│ ┌──────────────────┬────────────────────┬───────────────────┐ │
│ │ API Routes │ Core Logic │ Cron Jobs │ │
│ │ │ │ │ │
│ │ /api/slack/ │ • Message Parser │ • 09:00 브리핑 │ │
│ │ events │ • LLM Orchestrator │ • 매시 리마인드 │ │
│ │ /api/slack/ │ • Tool Executor │ • 월 주간리포트 │ │
│ │ interactions │ │ │ │
│ └──────────────────┴────────────────────┴───────────────────┘ │
└───────────┬─────────────────────┬─────────────────────┬─────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ Data Layer │ │ Integration Layer │ │ AI Layer │
│ (Supabase) │ │ (MCP Servers) │ │ (Claude API) │
│ │ │ │ │ │
│ • user_mappings │ │ • Notion MCP │ │ • 자연어 이해 │
│ • conversation_ │ │ • Calendar MCP │ │ • Tool Use │
│ context │ │ • Gmail MCP │ │ • 응답 생성 │
│ • notification_ │ │ │ │ │
│ settings │ │ │ │ │
└───────────────────┘ └─────────┬─────────┘ └───────────────────┘
│
▼
┌───────────────────────────┐
│ External Services │
│ │
│ 📋 Notion (Task DB) │
│ 📅 Google Calendar │
│ 📧 Gmail │
└───────────────────────────┘
Vercel Serverless Functions를 활용하여 모든 백엔드 로직을 처리한다.
| 엔드포인트 | 메서드 | 용도 |
|---|---|---|
/api/slack/events |
POST | Slack Event Subscriptions 수신 (멘션, DM 등) |
/api/slack/interactions |
POST | Slack Interactive Components (버튼 클릭 등) |
/api/health |
GET | 헬스체크 |
/lib
├── slack/
│ ├── parser.ts # 슬랙 메시지 파싱
│ ├── responder.ts # 슬랙 응답 전송
│ └── formatter.ts # 메시지 포맷팅 (Block Kit)
├── llm/
│ ├── orchestrator.ts # Claude API 호출 관리
│ ├── prompts.ts # 시스템 프롬프트 정의
│ └── tools.ts # Tool Use 함수 스키마
├── mcp/
│ ├── notion.ts # Notion MCP 클라이언트
│ ├── calendar.ts # Google Calendar MCP 클라이언트
│ └── gmail.ts # Gmail MCP 클라이언트
└── db/
└── supabase.ts # Supabase 클라이언트
vercel.json 설정:
{
"crons": [
{
"path": "/api/cron/morning-briefing",
"schedule": "0 0 * * *"
},
{
"path": "/api/cron/reminder-check",
"schedule": "0 * * * *"
},
{
"path": "/api/cron/weekly-report",
"schedule": "0 0 * * 1"
}
]
}참고: 시간은 UTC 기준. KST 09:00 = UTC 00:00
user_mappings
CREATE TABLE user_mappings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Slack 정보
slack_id VARCHAR(20) UNIQUE NOT NULL,
slack_username VARCHAR(100),
slack_display_name VARCHAR(100),
-- Notion 정보
notion_id VARCHAR(50),
notion_name VARCHAR(100),
-- Google 정보
google_email VARCHAR(100),
-- 메타데이터
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_user_mappings_slack_id ON user_mappings(slack_id);
CREATE INDEX idx_user_mappings_notion_id ON user_mappings(notion_id);
CREATE INDEX idx_user_mappings_google_email ON user_mappings(google_email);conversation_context
CREATE TABLE conversation_context (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slack_thread_ts VARCHAR(50) UNIQUE NOT NULL,
slack_channel_id VARCHAR(20) NOT NULL,
slack_user_id VARCHAR(20) NOT NULL,
-- 마지막으로 언급된 항목들
last_task_id VARCHAR(50),
last_task_name VARCHAR(200),
last_event_id VARCHAR(100),
last_email_id VARCHAR(100),
-- 대화 맥락 데이터 (유연한 확장용)
context_data JSONB,
-- TTL
expires_at TIMESTAMP DEFAULT (NOW() + INTERVAL '24 hours'),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_conversation_context_thread ON conversation_context(slack_thread_ts);notification_settings
CREATE TABLE notification_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slack_id VARCHAR(20) UNIQUE NOT NULL,
morning_briefing BOOLEAN DEFAULT true,
reminder_24h BOOLEAN DEFAULT true,
reminder_3h BOOLEAN DEFAULT true,
meeting_reminder BOOLEAN DEFAULT true,
quiet_hours_start TIME,
quiet_hours_end TIME,
created_at TIMESTAMP DEFAULT NOW()
);task_event_mapping (태스크-일정 연결)
CREATE TABLE task_event_mapping (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
notion_task_id VARCHAR(50) NOT NULL,
google_event_id VARCHAR(100) NOT NULL,
relationship_type VARCHAR(20) DEFAULT 'related', -- 'related', 'created_from', 'follow_up'
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(notion_task_id, google_event_id)
);
CREATE INDEX idx_task_event_mapping_task ON task_event_mapping(notion_task_id);
CREATE INDEX idx_task_event_mapping_event ON task_event_mapping(google_event_id);task_source_mapping (태스크 출처 추적)
CREATE TABLE task_source_mapping (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
notion_task_id VARCHAR(50) UNIQUE NOT NULL,
source_type VARCHAR(20) NOT NULL, -- 'gmail', 'slack', 'calendar', 'manual'
source_id VARCHAR(200), -- Gmail message ID, Slack thread ts 등
source_url TEXT, -- 원본 링크
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_task_source_mapping_task ON task_source_mapping(notion_task_id);
CREATE INDEX idx_task_source_mapping_source ON task_source_mapping(source_type, source_id);태스크-미팅 연결 기능을 사용하려면 노션에 별도의 미팅 DB를 생성해야 한다. 이 DB는 Google Calendar 이벤트와 노션 태스크를 연결하는 역할을 한다.
| 속성명 | 타입 | 필수 | 설명 |
|---|---|---|---|
| 미팅명 | 제목 | ✅ | 미팅 제목 |
| 일시 | 날짜 | ✅ | 미팅 시작 시간 |
| 참석자 | 사람(다중) | - | 미팅 참석자 |
| Calendar Event ID | 텍스트 | - | Google Calendar 이벤트 ID |
| 관련 태스크 | 관계형 | - | 태스크 DB와 연결 |
| 회의록 | 텍스트 | - | 미팅 노트 |
📌 이 DB는 선택적이며, 미팅-태스크 연결 기능을 사용하지 않는다면 생략 가능.
- 실시간 기능: 향후 실시간 알림 확장 가능
- Row Level Security: 필요시 권한 관리 용이
- PostgreSQL: 복잡한 쿼리 지원
- 무료 티어: 사내용 소규모 사용에 적합
Model Context Protocol을 통해 외부 서비스와 연동한다.
옵션 A: 기존 MCP 서버 활용
@modelcontextprotocol/server-notion@anthropic/mcp-server-google-calendar(또는 커스텀)- 커스텀 Gmail MCP 서버
옵션 B: 통합 MCP 서버 직접 구현
- Vercel 내에서 MCP 프로토콜 직접 구현
- 단일 서버로 Notion, Calendar, Gmail 모두 처리
| 서비스 | 인증 방식 | 비고 |
|---|---|---|
| Notion | Internal Integration Token | 팀 워크스페이스 전체 접근 |
| Google Calendar | Service Account + Domain-wide Delegation | 도메인 내 모든 사용자 캘린더 접근 |
| Gmail | Service Account + Domain-wide Delegation | 도메인 내 모든 사용자 메일 접근 |
| 기준 | Claude Sonnet 4.5 |
|---|---|
| Tool Calling 성능 | BFCL 벤치마크 70.29% (상위권) |
| MCP 호환성 | 네이티브 지원 (Anthropic이 MCP 창시) |
| 에이전트 안정성 | SWE-bench 77.2%, 장기 태스크 안정적 수행 |
| 한국어 지원 | 자연스러운 대화체 |
| 비용 | $3/$15 per 1M tokens (월 $100-300 예상) |
사용자 메시지
↓
[전처리] Slack 메시지 파싱 + 컨텍스트 로드
↓
[Claude API] 시스템 프롬프트 + 사용자 메시지 + Tools
↓
[Tool Use?] ─── Yes ──→ MCP 서버 호출 → 결과 반환 → Claude 재호출
│
No
↓
[응답 생성] 최종 텍스트 응답
↓
[후처리] Slack Block Kit 포맷팅 → 전송
const tools = [
// Notion
{ name: "get_tasks", description: "태스크 조회" },
{ name: "get_task_detail", description: "태스크 상세 조회" },
{ name: "update_task", description: "태스크 상태/속성 변경" },
{ name: "create_task", description: "새 태스크 생성" },
// Calendar
{ name: "get_calendar_events", description: "일정 조회" },
{ name: "create_calendar_event", description: "일정 생성" },
{ name: "update_calendar_event", description: "일정 수정" },
{ name: "delete_calendar_event", description: "일정 삭제" },
{ name: "check_availability", description: "가용 시간 확인" },
// Gmail
{ name: "get_emails", description: "메일 조회" },
{ name: "get_email_detail", description: "메일 상세 조회" },
{ name: "extract_action_items", description: "메일에서 액션 아이템 추출" },
// Cross-platform
{ name: "create_task_from_email", description: "메일 → 태스크 생성" },
{ name: "create_meeting_for_task", description: "태스크 → 미팅 생성" },
{ name: "generate_daily_briefing", description: "통합 브리핑 생성" },
];[1] 사용자: "@Paimy 이번 주 마감인 내 태스크 보여줘"
│
[2] Slack ─────────▶ Vercel /api/slack/events
│
[3] Vercel ────────▶ Supabase: user_mapping 조회 (slack_id → notion_id)
│
[4] Vercel ────────▶ Claude API: 의도 분석
│ └─ Tool 결정: get_tasks(owner=notion_id, due_date=this_week)
│
[5] Vercel ────────▶ MCP Notion: 태스크 조회
│
[6] MCP ───────────▶ Notion API → 결과 반환
│
[7] Vercel ────────▶ Claude API: 결과 요약 및 응답 생성
│
[8] Vercel ────────▶ Slack: 응답 메시지 전송
│
[9] 사용자: "📋 이번 주 마감 태스크 3건..."
[1] Vercel Cron: 09:00 (KST) 트리거
│
[2] Vercel ────────▶ Supabase: 브리핑 활성 사용자 목록 조회
│
[3] For each user:
│
├──▶ MCP Notion: 오늘 마감 태스크 조회
├──▶ MCP Calendar: 오늘 일정 조회
├──▶ MCP Gmail: 미읽은 중요 메일 조회
│
[4] Vercel ────────▶ Claude API: 브리핑 메시지 생성
│
[5] Vercel ────────▶ Slack: 사용자 DM으로 발송
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
# Supabase
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_SERVICE_KEY=...
# Claude
ANTHROPIC_API_KEY=sk-ant-...
# Notion
NOTION_INTEGRATION_TOKEN=secret_...
NOTION_TASK_DATABASE_ID=...
# Google (Service Account)
GOOGLE_SERVICE_ACCOUNT_EMAIL=[email protected]
GOOGLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----...
GOOGLE_DELEGATED_USER_EMAIL=[email protected] # 도메인 위임용paimy/
├── api/
│ ├── slack/
│ │ ├── events.ts
│ │ └── interactions.ts
│ ├── cron/
│ │ ├── morning-briefing.ts
│ │ ├── reminder-check.ts
│ │ └── weekly-report.ts
│ └── health.ts
├── lib/
│ ├── slack/
│ ├── llm/
│ ├── mcp/
│ └── db/
├── vercel.json
├── package.json
└── .env.local
main branch push
│
▼
Vercel CI/CD
│
├── Build & Deploy
├── Environment Variables (Vercel Dashboard)
└── Cron Jobs 활성화
- Vercel Serverless 함수 실행 시간 제한 (Pro: 60초, Hobby: 10초)
- Cron Jobs 최소 간격 1분 (Vercel Pro 필요)
| 상황 | 대응 |
|---|---|
| 처리 시간 초과 | Vercel Background Functions 또는 외부 큐(Upstash) 활용 |
| 실시간 알림 필요 | Supabase Realtime + Slack Socket Mode |
| 트래픽 급증 | Edge Functions 활용, Caching 강화 |
- Function Logs (실시간)
- Analytics (요청 수, 응답 시간)
- Error Tracking
- Supabase에
logs테이블 생성하여 주요 이벤트 기록 - Slack에
#paimy-logs채널 생성하여 에러 알림