You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.1 KiB

Phase 16 — 실제 이메일 계정 연동 (Gmail + Outlook)

mock 시드 메일을 넘어 내 Gmail·Outlook 계정을 OAuth로 붙여 받은편지함을 수집하고, 회신/새 메일을 결재 승인 시 실제 발송한다. mock 데모는 1바이트도 바뀌지 않는다 (CONNECTOR_MAIL=mock 기본, real은 연결된 계정에만 적용).

이 문서는 OAuth 앱 자격증명 발급(Google Cloud · Azure) 가이드와 동작 확인 절차다. 코드는 이미 구현되어 있고, 자격증명만 backend/.env에 넣으면 실연동이 켜진다.


1. 무엇이 동작하나

  • Gmail: Google OAuth(Authorization Code + PKCE) → Gmail REST API로 받은편지함 증분 수집 + 발송.
  • Outlook / Microsoft 365: Microsoft Graph OAuth → /me/messages 수집 + /me/sendMail 발송.
  • Outlook / M365 캘린더: 같은 Microsoft 앱 등록에 Calendars.Read 권한만 더하면 /me/calendarView로 일정 수집(메일과 별개 계정 "Outlook 캘린더 추가"로 붙음, 설정 → 일정 탭).
  • 회사 M365: 개인 Outlook.com과 같은 앱 하나로 처리(테넌트 common). 회사 계정은 "Outlook 추가"를 한 번 더 눌러 로그인하면 별도 계정으로 붙음. 단 회사가 외부 앱 동의를 잠가뒀으면 "관리자 승인 필요"가 떠서 IT에 이 앱(클라이언트 ID) 승인을 요청해야 함.
  • 멀티계정: 같은 제공자라도 이메일 주소가 다르면 별도 계정으로 추가(덮어쓰기 없음).
  • 읽기 + 보내기: 메일 페이지의 회신/새 메일은 high-risk 결재함으로 가고, 승인 순간 연결된 real 계정이면 진짜 발송, 미연결이면 보낸편지함(mock) 기록.

왜 OAuth인가 (SMTP/IMAP 비밀번호 불가): Gmail은 2022년 basic-auth(앱 비밀번호 제외)를, Outlook.com(개인)은 2024-09-16부로 IMAP/POP/SMTP basic-auth를, M365(직장/학교)는 그 이전에 차단했다. Outlook은 OAuth2가 유일한 경로라 둘 다 OAuth로 통일했다.


2. Google Cloud — Gmail OAuth 클라이언트 발급

  1. https://console.cloud.google.com → 프로젝트 생성(예: ari-mail).
  2. API 및 서비스 → 라이브러리Gmail API 사용 설정.
  3. OAuth 동의 화면:
    • User Type = 외부(External), 앱 이름/지원 이메일 입력 후 저장.
    • 게시 상태 = 테스트(Testing) 로 두고, 테스트 사용자에 본인 Gmail 주소 추가. (테스트 모드면 Google 검수 없이 본인 계정으로 바로 사용 가능 — 개인용은 이걸로 충분.)
    • 스코프는 굳이 추가 안 해도 됨(요청 시 동적으로 동의 화면에 표시됨).
  4. 사용자 인증 정보 → 사용자 인증 정보 만들기 → OAuth 클라이언트 ID:
    • 애플리케이션 유형 = 웹 애플리케이션.
    • 승인된 리디렉션 URI = http://localhost:31800/api/connectors/oauth/callback
    • 만들면 클라이언트 ID / 클라이언트 보안 비밀 발급 → 복사.

요청 스코프(코드가 자동 요청): gmail.readonly, gmail.send (연결 계정 이메일은 users/me/profile로 조회 — 추가 스코프 불필요).


3. Azure Portal — Outlook(Microsoft Graph) 앱 등록

  1. https://portal.azure.comMicrosoft Entra ID → 앱 등록 → 새 등록.
  2. 지원되는 계정 유형 = "모든 조직 디렉터리 + 개인 Microsoft 계정"(개인 Outlook.com 포함).
    • 이 경우 테넌트는 기본 common(아래 .env MICROSOFT_TENANT 기본값).
  3. 리디렉션 URI = 플랫폼 , http://localhost:31800/api/connectors/oauth/callback
  4. 인증서 및 비밀 → 새 클라이언트 비밀 생성 → 값(Value) 복사(이때만 보임).
  5. API 권한 → 권한 추가 → Microsoft Graph → 위임된 권한:
    • 메일: Mail.Read, Mail.Send, User.Read, offline_access 추가.
    • 일정도 쓰려면: Calendars.Read 추가(이걸 넣으면 설정 → 일정 탭에 "Outlook 캘린더 추가"가 활성).
    • 개인 계정은 관리자 동의 불필요(동의 화면에서 본인이 동의). 회사 M365는 테넌트 정책에 따라 "관리자 승인 필요"가 뜰 수 있음 → 별도 앱이 아니라 IT에 이 앱 승인을 요청.
  6. 개요에서 애플리케이션(클라이언트) ID 복사.

4. backend/.env 주입

# 토큰 암호화 키(실연동 필수 — 32바이트 이상 임의 문자열)
ARI_SECRET_KEY=<openssl rand -hex 32 등으로 생성한 강한 키>

# real 메일 연동 ON (phase-16+: 메일은 mock 제거됨 — 연결 전까지 빈 상태)
CONNECTOR_MAIL=real

# Google (Gmail)
GOOGLE_CLIENT_ID=<2단계에서 발급>
GOOGLE_CLIENT_SECRET=<2단계에서 발급>

# Microsoft (Outlook)
MICROSOFT_CLIENT_ID=<3단계에서 발급>
MICROSOFT_CLIENT_SECRET=<3단계의 비밀 '값'>
MICROSOFT_TENANT=common

# google·microsoft 공용 콜백(양쪽 콘솔에 등록한 것과 정확히 일치해야 함)
OAUTH_REDIRECT_URI=http://localhost:31800/api/connectors/oauth/callback

ARI_SECRET_KEY를 바꾸면 기존에 암호화 저장된 토큰은 복호화 불가(재연결 필요). 프로덕션에선 HTTPS 콜백 URL로 바꾸고 각 콘솔에도 그 URL을 등록한다.


5. 동작 확인

  1. 백엔드 기동: cd backend && uv run uvicorn app.main:app --port 31800 프론트 기동: cd frontend && pnpm dev (:31300).
  2. /settings?tab=mail계정 추가 버튼 → Gmail(또는 Outlook) 선택.
    • 자격증명이 설정됐으면 항목이 활성화됨(미설정이면 "설정 필요"로 비활성).
  3. 제공자 동의 화면 → 허용 → 콜백이 /settings?tab=mail?connect=ok로 복귀하며 토스트.
  4. 연결 직후 1회 초기 sync로 받은편지함 일부가 수집됨 → 메일 페이지에 그 계정 탭으로 노출.
  5. 다른 이메일로 한 번 더 추가하면 별도 계정으로 붙음(멀티계정).
  6. 메일 회신/새 메일 작성 → 결재함에 pending → 승인 → 실제 수신함으로 발송 확인. (연결 안 된 계정에서 보내면 보낸편지함 mock 기록만 남고 외부 발송은 안 됨 = 데모 안전.)

6. 구현 노트 (파일 맵)

영역 파일 변경
OAuth backend/app/connectors/oauth.py microsoft provider, _family(outlook→microsoft), gmail.send 스코프, _fetch_identity(프로필 이메일), 멀티계정 _ensure_account+MailAccount upsert
설정 backend/app/config.py microsoft_client_id/secret, microsoft_tenant, oauth_redirect_uri
커넥터 backend/app/connectors/mail/real_outlook.py (신규), real_gmail.py(send_mail), normalize.py(outlook 분기 + real 계정 매핑), registry.py(provider별 Gmail/Outlook 분기)
발송 backend/app/models.py(OutboundMail), 마이그레이션 p17e5f6a7b8c9, routers/mail.py(send()), connectors/mail/outbound.py(send_outbound), worker/main.py(approval.executed 구독)
라우터 backend/app/routers/connectors.py /connectors/providers, oauth_start outlook 검증, 콜백 redirect_after 반영
프론트 frontend/components/connectors/AddAccountMenu.tsx(신규), settings/ConnectorList.tsx·MailTab.tsx·SettingsClient.tsx(connect 토스트), lib/connectors/api.ts(providers/oauthStart), lib/types.ts

설계 원칙

  • effective_mode: 시드 mock 계정은 env=real이어도 mock 유지(데모 결정성). real은 연결된 계정에만.
  • 토큰은 app/crypto.py Fernet로 암호화 저장(평문 금지), 만료 시 valid_access_token이 refresh.
  • 수집은 ExternalLink 기준 멱등 upsert(중복 없음), 발송 실패는 OutboundMail.status=failed로 격리.
  • 발송 훅은 동기 event_bus 구독이라 WORKER_ENABLED=false(기본) 데모에서도 승인 즉시 처리. 테스트는 lifespan 미가동이라 자동 발송 안 됨 → send_outbound 헬퍼를 직접 단위 테스트.

7. 범위 밖(후속)

첨부 다운로드/발송, 라벨 양방향 동기화, push/webhook(현재 polling), 프로덕션 OAuth 검수, 멀티유저.