# Email Skill 통합 현황 및 TODO ## 작성일: 2025-08-13 ## 작성자: Claude (with heejae) --- ## 1. 현재 상황 ### 1.1 시스템 구조 ``` User → rb8001 → skill-email → Gmail API ↓ router.py가 라우팅 ↓ skill-email:8501 ``` ### 1.2 발생한 문제 **오류**: `422 Unprocessable Entity` **원인 1**: Gmail OAuth 토큰 파일 없음 - 필요 위치: `/home/admin/auth-server/tokens/test_gmail.json` - 현재 상태: 디렉토리는 있지만 토큰 파일 없음 **원인 2**: 잘못된 요청 형식 ```python # 현재 rb8001이 보내는 payload { "message": "이메일 보내줘", "user_id": "U091UNVE41M", "channel": "C123456", "robeing_id": "rb8001", "action": "compose", "execution_plan": {...} } # skill-email이 기대하는 payload { "to": "recipient@example.com", "subject": "제목", "body": "내용", "user_id": "test" # Gmail 토큰 식별용 } ``` --- ## 2. 토큰 관리 방안 ### 2.1 현재 구조 (파일 기반) ```yaml 위치: /home/admin/auth-server/tokens/ 형식: {user_id}_gmail.json 내용: - token: "access_token" - refresh_token: "refresh_token" - client_id: "xxx.apps.googleusercontent.com" - client_secret: "secret" - scopes: ["gmail.send", "gmail.readonly"] ``` ### 2.2 제안: PostgreSQL 기반 관리 ```sql -- Gmail 토큰 테이블 CREATE TABLE gmail_token ( id SERIAL PRIMARY KEY, user_id VARCHAR(100) UNIQUE NOT NULL, robeing_id VARCHAR(50), token TEXT NOT NULL, refresh_token TEXT NOT NULL, token_uri VARCHAR(255), client_id VARCHAR(255), client_secret VARCHAR(255), scopes TEXT, -- JSON array expiry TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` **장점**: - 중앙 집중식 관리 - 여러 서버에서 접근 가능 - 백업/복구 용이 - 토큰 갱신 이력 추적 --- ## 3. 아키텍처 개선 방안 ### 3.1 현재 문제점 - rb8001이 이메일 구조를 알아야 함 - skill-email이 자연어 처리 못함 - 여러 로빙이 하나의 skill-email 공유 시 충돌 ### 3.2 개선안: 하이브리드 접근 ```python # rb8001 - 의도 파악만 async def route_email(message): intent = extract_intent(message) # "이메일 보내줘" hints = extract_entities(message) # {recipient_hint: "희재"} return await skill_email.compose({ "original_message": message, "hints": hints, "slack_user_id": user_id, "robeing_id": robeing_id }) # skill-email - 이메일 생성 및 발송 async def compose_email(request): # 1. 자연어에서 이메일 정보 추출 email_data = await parse_email_request( request.original_message, request.hints ) # 2. 부족한 정보 확인 if not email_data.complete: return {"need_info": ["recipient", "subject"]} # 3. Gmail 토큰 확인 token = await get_token(request.slack_user_id) if not token: return {"error": "No Gmail token"} # 4. 발송 return await send_via_gmail(email_data, token) ``` --- ## 4. TODO List ### Phase 1: 긴급 수정 (토큰 없이 테스트) - [ ] skill-email에 mock 모드 추가 ```python @app.post("/compose") async def compose_mock(request): # 이메일 구성만, 실제 발송 안 함 return { "status": "composed", "preview": { "to": "test@example.com", "subject": f"Re: {request.message[:30]}", "body": f"About: {request.message}" }, "error": "No Gmail token (mock mode)" } ``` - [ ] rb8001 router 수정 - 이메일 intent 감지 로직 개선 - skill-email로 원본 메시지 전달 ### Phase 2: 토큰 시스템 구축 - [ ] Gmail OAuth 플로우 구현 ```python # auth-server에 추가 @app.get("/oauth/gmail/authorize") async def gmail_auth(): # Google OAuth URL 생성 return redirect(google_oauth_url) @app.get("/oauth/gmail/callback") async def gmail_callback(code: str): # 토큰 교환 및 저장 token = exchange_code_for_token(code) save_token_to_db(token) ``` - [ ] PostgreSQL 토큰 테이블 생성 - [ ] robeing-state-service에 토큰 API 추가 ### Phase 3: 사용자 매핑 - [ ] Slack User ID → Gmail 계정 매핑 ```python USER_GMAIL_MAPPING = { "U0925SXQFDK": "heejae", # 희재 "U091UNVE41M": "jongtae", # 종태 "U092HQMUXMB": "hanyong" # 한용 } ``` - [ ] 로빙별 발신 계정 설정 ```python ROBEING_GMAIL_MAPPING = { "rb8001": "robeing.main", "rb10408": "robeing.test", "rb10508": "robeing.micro" } ``` ### Phase 4: LLM 통합 - [ ] skill-email에 경량 LLM 추가 (선택사항) - [ ] 이메일 템플릿 시스템 - [ ] 주소록 검색 기능 ### Phase 5: 프로덕션 준비 - [ ] 토큰 자동 갱신 (refresh_token 사용) - [ ] 에러 처리 및 재시도 로직 - [ ] 발송 이력 저장 - [ ] 보안 검토 (토큰 암호화) --- ## 5. 서버 정보 ### Auth 서버 (PostgreSQL 위치) ```bash Host: 124.55.18.179 Port: 51123 User: heejae Database: main_db PostgreSQL User: robeings/robeings ``` ### SSH 터널 (현재 설정) ```bash # 로컬 5433 → 원격 5432 ssh -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 ``` ### 관련 서비스 - **rb8001**: 메인 로빙 (포트 8001) - **skill-email**: 이메일 스킬 (포트 8501) - **robeing-state-service**: 상태 관리 (포트 8507) --- ## 6. 결정 필요 사항 ### Q1: LLM 위치 - **Option A**: skill-email에 자체 LLM (독립적) - **Option B**: rb8001에서 처리 후 전달 (중앙 집중) - **Option C**: 하이브리드 (기본은 rb8001, 복잡한 경우 skill-email) ✅ ### Q2: 토큰 저장 방식 - **Option A**: 파일 시스템 (현재) - **Option B**: PostgreSQL (제안) ✅ - **Option C**: Redis (임시 캐시) ### Q3: 멀티 로빙 지원 - 각 로빙이 다른 Gmail 계정 사용? - 사용자별로 Gmail 계정 매핑? - 공용 계정 하나로 통합? --- ## 7. 참고 코드 ### 현재 skill-email 구조 ```python # main.py TOKEN_BASE = os.getenv("TOKEN_BASE", "../auth-server/tokens") # services/gmail_service.py class FileCredentialsProvider: def get_credentials(self, user_id: str): token_path = self.token_base / f"{user_id}_gmail.json" # ... 토큰 로드 ``` ### 제안하는 DB 기반 구조 ```python class DBCredentialsProvider: async def get_credentials(self, user_id: str): # PostgreSQL에서 토큰 조회 token = await db.query( "SELECT * FROM gmail_token WHERE user_id = %s", user_id ) return Credentials(**token) ``` --- ## 8. 다음 단계 1. **즉시 실행**: Mock 모드로 이메일 스킬 테스트 2. **단기 과제**: Gmail OAuth 토큰 생성 및 저장 3. **중기 과제**: PostgreSQL 기반 토큰 관리 시스템 4. **장기 과제**: 멀티 로빙 및 사용자별 계정 관리 --- ## 로그 예시 ``` INFO: Calling service: http://172.17.0.1:8501/send with payload keys: ['message', 'user_id', 'channel', 'robeing_id', 'action', 'execution_plan'] ERROR: 422 Unprocessable Entity DETAIL: 토큰 파일이 없습니다: /app/auth-server/tokens/test_gmail.json ```