DOCS/250817_email_skill_integration_status.md
happybell80 6690919bad docs: 문서 파일명에 작성 날짜 추가
- 250814_rb8001_통합_트러블슈팅.md (8월 14일 작성)
- 250817_email_skill_integration_status.md (8월 17일 작성)
- 250817_slack_user_mapping_troubleshooting.md (8월 17일 작성)

일관된 파일명 형식: YYMMDD_제목.md
2025-08-17 23:49:13 +09:00

7.2 KiB

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: 잘못된 요청 형식

# 현재 rb8001이 보내는 payload
{
    "message": "이메일 보내줘",
    "user_id": "U091UNVE41M", 
    "channel": "C123456",
    "robing_id": "rb8001",
    "action": "compose",
    "execution_plan": {...}
}

# skill-email이 기대하는 payload
{
    "to": "recipient@example.com",
    "subject": "제목",
    "body": "내용",
    "user_id": "test"  # Gmail 토큰 식별용
}

2. 토큰 관리 방안

2.1 현재 구조 (파일 기반)

위치: /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 기반 관리

-- Gmail 토큰 테이블
CREATE TABLE gmail_tokens (
    id SERIAL PRIMARY KEY,
    user_id VARCHAR(100) UNIQUE NOT NULL,
    robing_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 개선안: 하이브리드 접근

# 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,
        "robing_id": robing_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 모드 추가

    @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 플로우 구현

    # 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 계정 매핑

    USER_GMAIL_MAPPING = {
        "U0925SXQFDK": "heejae",     # 희재
        "U091UNVE41M": "jongtae",    # 종태
        "U092HQMUXMB": "hanyong"     # 한용
    }
    
  • 로빙별 발신 계정 설정

    ROBING_GMAIL_MAPPING = {
        "rb8001": "robeing.main",
        "rb10408": "robeing.test",
        "rb10508": "robeing.micro"
    }
    

Phase 4: LLM 통합

  • skill-email에 경량 LLM 추가 (선택사항)
  • 이메일 템플릿 시스템
  • 주소록 검색 기능

Phase 5: 프로덕션 준비

  • 토큰 자동 갱신 (refresh_token 사용)
  • 에러 처리 및 재시도 로직
  • 발송 이력 저장
  • 보안 검토 (토큰 암호화)

5. 서버 정보

Auth 서버 (PostgreSQL 위치)

Host: 124.55.18.179
Port: 51123
User: heejae
Database: auth_db
PostgreSQL User: robeings/robeings

SSH 터널 (현재 설정)

# 로컬 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 구조

# 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 기반 구조

class DBCredentialsProvider:
    async def get_credentials(self, user_id: str):
        # PostgreSQL에서 토큰 조회
        token = await db.query(
            "SELECT * FROM gmail_tokens 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', 'robing_id', 'action', 'execution_plan']
ERROR: 422 Unprocessable Entity
DETAIL: 토큰 파일이 없습니다: /app/auth-server/tokens/test_gmail.json