From 73d64d4b035a00cbf63dce04d783ee2e763c6cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Wed, 11 Mar 2026 22:59:30 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[email]=20=ED=85=8C=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=97=B0=EB=8F=99=20=EA=B0=80?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테넌트 메일 연동 기술문서 신규 작성 (SMTP 프리셋, MNG 관리 화면, 연결 테스트) - 기존 email-policy.md에 연동 가이드 참조 추가 - INDEX.md에 이메일 연동 문서 등록 --- INDEX.md | 4 +- dev/guides/tenant-email-integration-guide.md | 741 +++++++++++++++++++ dev/standards/email-policy.md | 1 + 3 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 dev/guides/tenant-email-integration-guide.md diff --git a/INDEX.md b/INDEX.md index cdb46a5..cd00bb8 100644 --- a/INDEX.md +++ b/INDEX.md @@ -16,7 +16,8 @@ | Git 커밋 | `dev/standards/git-conventions.md` | 커밋 메시지, 브랜치 전략 | | 품질 검증 | `dev/standards/quality-checklist.md` | 코드 품질 체크리스트 | | Swagger | `dev/guides/swagger-guide.md` | API 문서 작성법 | -| 이메일 | `dev/standards/email-policy.md` | 멀티테넌시 이메일 정책 | +| 이메일 정책 | `dev/standards/email-policy.md` | 멀티테넌시 이메일 발송 아키텍처 | +| 이메일 연동 | `dev/guides/tenant-email-integration-guide.md` | 테넌트 메일 연동, SMTP 프리셋, MNG 관리 | | 품목관리 | `rules/item-policy.md` | 품목 정책 | | 단가관리 | `rules/pricing-policy.md` | 원가/판매가, 리비전 | | 견적관리 | `features/quotes/README.md` | 견적 시스템, BOM 계산 | @@ -167,6 +168,7 @@ DB 도메인별: | [erp-api-detail.md](dev/guides/erp-api-detail.md) | ERP API 상세 | | [item-master-guide.md](dev/guides/item-master-guide.md) | 품목기준관리 구조 | | [claude-code-to-slack.md](dev/guides/claude-code-to-slack.md) | Claude Code → 슬랙 붙여넣기 가이드 | +| [tenant-email-integration-guide.md](dev/guides/tenant-email-integration-guide.md) | 테넌트 이메일 연동 (SMTP 프리셋, MNG 관리 화면, 연결 테스트) | --- diff --git a/dev/guides/tenant-email-integration-guide.md b/dev/guides/tenant-email-integration-guide.md new file mode 100644 index 0000000..cd4fbb1 --- /dev/null +++ b/dev/guides/tenant-email-integration-guide.md @@ -0,0 +1,741 @@ +# 테넌트 이메일 연동 가이드 + +> **작성일**: 2026-03-11 +> **상태**: 설계 확정 +> **관련 정책**: [이메일 발송 정책](../standards/email-policy.md) + +--- + +## 1. 개요 + +### 1.1 목적 + +SAM 멀티테넌시 환경에서 각 테넌트(회사)가 사용하는 다양한 메일 시스템을 SAM과 연동하는 방법을 정의한다. +**본사(코드브릿지엑스)가 MNG 관리 화면에서 각 테넌트의 메일 설정을 대행 관리**하는 방식으로 운영한다. + +### 1.2 핵심 원칙 + +| 원칙 | 설명 | +|------|------| +| 🔴 본사 중앙 관리 | 테넌트 메일 설정은 MNG 관리자(본사)가 등록/수정한다 | +| 🔴 프리셋 기반 | 주요 메일 제공자별 SMTP 프리셋을 제공하여 설정 오류를 방지한다 | +| 🟡 연결 테스트 필수 | 설정 저장 전 SMTP 연결 테스트를 통과해야 한다 | +| 🟡 단계적 확장 | 플랫폼 발송(Phase 1) → SMTP 릴레이(Phase 2) → OAuth2(Phase 3) | +| 🟢 가이드 제공 | 테넌트에게 앱 비밀번호 생성 등 사전 준비 절차를 안내한다 | + +### 1.3 운영 모델 + +``` +본사 (코드브릿지엑스) 테넌트 (고객사) +┌────────────────────────┐ ┌────────────────────────┐ +│ MNG 관리 화면 │ │ │ +│ ├── 테넌트 목록 │ SMTP 정보 │ 기존 메일 시스템 │ +│ ├── 메일 설정 등록 │ ←──── 전달 ──│ (Gmail, Naver 등) │ +│ ├── SMTP 프리셋 선택 │ │ │ +│ ├── 연결 테스트 │ │ 앱 비밀번호 생성 │ +│ └── 발송 현황 모니터링 │ │ (본사 안내에 따라) │ +└────────────────────────┘ └────────────────────────┘ +``` + +> **테넌트는 MNG에 접근하지 않는다.** 본사 관리자가 테넌트로부터 SMTP 정보를 전달받아 MNG에서 설정한다. + +--- + +## 2. 테넌트 메일 환경 현황 + +### 2.1 한국 기업 메일 시스템 분류 + +| 유형 | 주로 사용하는 메일 | 예시 도메인 | 비율 (추정) | +|------|-------------------|------------|------------| +| 포털 메일 | 네이버, 다음 | `ceo@naver.com` | 소기업 30% | +| 호스팅 메일 | 카페24, 가비아 | `admin@company.co.kr` | 중소기업 25% | +| Google Workspace | Gmail 인프라 | `hong@company.com` | 중소~중견 20% | +| 네이버웍스 | 네이버 인프라 | `hong@company.com` | 중소기업 15% | +| Microsoft 365 | Exchange/Outlook | `hong@company.co.kr` | 중견~대기업 8% | +| 자체 메일 서버 | On-premise | `hong@company.com` | 대기업 2% | + +### 2.2 메일 제공자별 SMTP 정보 + +| 제공자 | SMTP 호스트 | 포트 | 암호화 | 인증 방식 | 일일 한도 | +|--------|------------|------|--------|----------|----------| +| Gmail / Google Workspace | `smtp.gmail.com` | 587 | TLS | 앱 비밀번호 | 500건 (무료) / 2,000건 (Workspace) | +| 네이버 | `smtp.naver.com` | 587 | TLS | 앱 비밀번호 | 500건 | +| 네이버웍스 | `smtp.worksmobile.com` | 587 | TLS | 앱 비밀번호 | 관리자 설정 | +| 다음/카카오 | `smtp.daum.net` | 465 | SSL | 앱 비밀번호 | 500건 | +| Microsoft 365 | `smtp.office365.com` | 587 | STARTTLS | OAuth2 권장 | 10,000건 | +| 카페24 | 업체별 상이 | 587 | TLS | ID/PW | 업체별 상이 | +| 가비아 | `smtp.gabia.com` | 587 | TLS | ID/PW | 업체별 상이 | + +--- + +## 3. 연동 방식 (3단계 전략) + +### 3.1 Phase 1: 플랫폼 발송 + Reply-To (즉시 적용) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Phase 1 — 플랫폼 발송 │ +│ │ +│ SAM 공용 SMTP로 발송하되, 테넌트 브랜딩 적용 │ +│ │ +│ From: "주일블라인드" │ +│ Reply-To: admin@juil-blind.co.kr │ +│ Subject: [SAM] 전자서명 요청 │ +│ │ +│ 테넌트 설정: from_name + reply_to만 입력하면 됨 │ +│ provider: 'platform' │ +└─────────────────────────────────────────────────────────┘ +``` + +**장점**: 테넌트가 SMTP 정보를 제공하지 않아도 즉시 사용 가능 +**단점**: 발신 주소가 `@sam.codebridge-x.com`으로 보임 (신뢰도 하락 가능) + +**적합 대상**: 메일 설정이 어렵거나 SMTP 정보를 제공하지 않는 테넌트 + +### 3.2 Phase 2: SMTP 릴레이 (핵심 — 대부분의 테넌트) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Phase 2 — SMTP 릴레이 │ +│ │ +│ 테넌트의 SMTP 서버를 통해 발송 │ +│ │ +│ From: "주일블라인드" │ +│ Subject: 전자서명 요청 │ +│ │ +│ 테넌트 설정: SMTP 프리셋 선택 + 계정/비밀번호 입력 │ +│ provider: 'smtp' │ +│ → 수신자에게는 테넌트 회사 메일에서 온 것처럼 보임 │ +└─────────────────────────────────────────────────────────┘ +``` + +**장점**: 테넌트 도메인으로 발송, 높은 신뢰도 +**단점**: 테넌트에게 앱 비밀번호 생성을 요청해야 함 + +**적합 대상**: SMTP 접근 가능한 대부분의 테넌트 + +### 3.3 Phase 3: OAuth2 연동 (고급 — 향후) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Phase 3 — OAuth2 연동 │ +│ │ +│ Google / Microsoft 계정 OAuth2 인증 │ +│ 비밀번호 저장 없이 토큰 기반 발송 │ +│ │ +│ provider: 'google_oauth' | 'microsoft_oauth' │ +│ → 가장 안전하지만 구현 복잡도 높음 │ +└─────────────────────────────────────────────────────────┘ +``` + +**장점**: 비밀번호 저장 불필요, 보안 최상 +**단점**: 구현 복잡도 높음, Google/MS만 지원, Azure AD 등록 필요 + +**적합 대상**: Google Workspace / Microsoft 365 사용 중견기업 이상 + +### 3.4 단계별 구현 우선순위 + +| Phase | 범위 | 대상 테넌트 | 구현 난이도 | 우선순위 | +|-------|------|-----------|------------|---------| +| Phase 1 | 플랫폼 발송 + Reply-To | 전체 (기본값) | 낮음 | 🔴 필수 | +| Phase 2 | SMTP 릴레이 + 프리셋 | SMTP 정보 제공 가능 | 중간 | 🔴 필수 | +| Phase 3 | OAuth2 연동 | Google/MS 365 사용 업체 | 높음 | 🟢 권장 | + +--- + +## 4. SMTP 프리셋 시스템 + +### 4.1 프리셋 설정 파일 + +> **위치**: `/home/aweso/sam/mng/config/mail-presets.php` (MNG) +> `/home/aweso/sam/api/config/mail-presets.php` (API) + +```php + [ + 'label' => 'Gmail / Google Workspace', + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'gmail', + 'daily_limit' => 500, + 'notes' => '앱 비밀번호 필요 (2단계 인증 활성화 후 생성)', + ], + 'naver' => [ + 'label' => '네이버 메일', + 'host' => 'smtp.naver.com', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'naver', + 'daily_limit' => 500, + 'notes' => '네이버 메일 설정 > POP3/SMTP > SMTP 사용 활성화 필요', + ], + 'naverworks' => [ + 'label' => '네이버웍스 (NAVER WORKS)', + 'host' => 'smtp.worksmobile.com', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'naverworks', + 'daily_limit' => null, + 'notes' => '관리자 콘솔에서 SMTP 허용 설정 필요', + ], + 'daum' => [ + 'label' => '다음/카카오 메일', + 'host' => 'smtp.daum.net', + 'port' => 465, + 'encryption' => 'ssl', + 'guide_key' => 'daum', + 'daily_limit' => 500, + 'notes' => '카카오 계정 > 보안 > 앱 비밀번호 생성', + ], + 'microsoft365' => [ + 'label' => 'Microsoft 365 (Outlook)', + 'host' => 'smtp.office365.com', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'microsoft365', + 'daily_limit' => 10000, + 'notes' => 'Basic Auth 폐지 주의 — OAuth2 전환 권장', + ], + 'cafe24' => [ + 'label' => '카페24 호스팅 메일', + 'host' => '', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'cafe24', + 'daily_limit' => null, + 'notes' => 'SMTP 호스트는 호스팅 계정마다 상이 — 수동 입력', + ], + 'gabia' => [ + 'label' => '가비아 호스팅 메일', + 'host' => 'smtp.gabia.com', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => 'gabia', + 'daily_limit' => null, + 'notes' => '가비아 메일 관리 > SMTP 설정 확인', + ], + 'custom' => [ + 'label' => '직접 입력 (자체 서버 등)', + 'host' => '', + 'port' => 587, + 'encryption' => 'tls', + 'guide_key' => null, + 'daily_limit' => null, + 'notes' => '호스트, 포트, 암호화 방식을 직접 입력', + ], +]; +``` + +### 4.2 프리셋 선택 흐름 + +``` +본사 관리자가 MNG에서 테넌트 메일 설정 + │ + ├── 1. 테넌트 선택 + │ + ├── 2. 발송 방식 선택 + │ ├── "SAM 기본" → Phase 1 (from_name + reply_to만 입력) + │ └── "자체 SMTP" → Phase 2 (프리셋 선택 진행) + │ + ├── 3. 프리셋 선택 (자체 SMTP 시) + │ ├── [Gmail ▼] 선택 → host/port/encryption 자동 채움 + │ ├── [네이버 ▼] 선택 → host/port/encryption 자동 채움 + │ └── [직접 입력] 선택 → 모든 필드 수동 입력 + │ + ├── 4. 계정 정보 입력 + │ ├── SMTP 사용자명 (이메일 주소) + │ └── SMTP 비밀번호 (앱 비밀번호) + │ + ├── 5. 연결 테스트 [🔧 테스트] 버튼 + │ ├── 성공 → "SMTP 연결 성공 (230ms)" ✅ + │ └── 실패 → 에러 메시지 + 트러블슈팅 안내 + │ + └── 6. 저장 (테스트 통과 시에만 활성화) +``` + +--- + +## 5. MNG 관리 화면 설계 + +### 5.1 메뉴 위치 + +``` +MNG 사이드바 +└── 시스템 관리 + └── 테넌트 관리 + └── 이메일 설정 ← 신규 메뉴 +``` + +> **라우트**: `GET /system/tenants/{tenant}/mail-config` +> **컨트롤러**: `App\Http\Controllers\System\TenantMailConfigController` + +### 5.2 화면 구조 — 테넌트 메일 설정 폼 + +``` +┌─ 이메일 설정 — [주일블라인드] ──────────────────────────┐ +│ │ +│ ── 기본 정보 ────────────────────────────────────────── │ +│ 발신자명: [주일블라인드 ] │ +│ 발신 이메일: [admin@juil-blind.co.kr] │ +│ 회신 주소: [admin@juil-blind.co.kr] (선택) │ +│ 일일 발송 한도: [500] 건 │ +│ │ +│ ── 발송 방식 ────────────────────────────────────────── │ +│ ● SAM 기본 (플랫폼 SMTP) │ +│ 발신 주소: noreply@sam.codebridge-x.com │ +│ Reply-To에 위 발신 이메일 자동 적용 │ +│ │ +│ ○ 자체 SMTP │ +│ ┌─ SMTP 설정 ──────────────────────────────────────┐ │ +│ │ 프리셋: [Gmail / Google Workspace ▼] │ │ +│ │ ℹ️ 앱 비밀번호 필요 (가이드 보기) │ │ +│ │ │ │ +│ │ 호스트: [smtp.gmail.com ] (자동) │ │ +│ │ 포트: [587 ] (자동) │ │ +│ │ 암호화: [TLS ▼ ] (자동) │ │ +│ │ 사용자명: [admin@juil-blind.co.kr] │ │ +│ │ 비밀번호: [•••••••••••••••• ] │ │ +│ │ │ │ +│ │ [🔧 연결 테스트] │ │ +│ │ │ │ +│ │ 테스트 결과: │ │ +│ │ ✅ SMTP 연결 성공 (응답시간: 230ms) │ │ +│ │ ✅ 테스트 메일 발송 완료 │ │ +│ │ → admin@juil-blind.co.kr로 전송 │ │ +│ └────────────────────────────────────────────────────┘ │ +│ │ +│ ── 브랜딩 (선택) ────────────────────────────────────── │ +│ 회사명: [주일블라인드 ] │ +│ 로고: [📎 업로드] sam_bi_logo.png │ +│ 테마 컬러: [#1a56db] 🎨 │ +│ 회사 주소: [서울시 강남구... ] │ +│ 연락처: [02-1234-5678 ] │ +│ 푸터 문구: [SAM 시스템에서 발송된 메일입니다] │ +│ │ +│ ── 상태 ─────────────────────────────────────────────── │ +│ 설정 상태: ✅ 활성 | 마지막 테스트: 2026-03-11 14:30 │ +│ 오늘 발송: 23/500건 | 이번 달 총 발송: 456건 │ +│ │ +│ [💾 저장] [↩️ 취소] │ +└──────────────────────────────────────────────────────────┘ +``` + +### 5.3 화면 구조 — 테넌트 목록 (메일 설정 현황) + +``` +┌─ 테넌트 이메일 설정 현황 ────────────────────────────────┐ +│ │ +│ ┌────┬──────────┬──────────┬────────┬───────┬───────┐ │ +│ │ ID │ 테넌트명 │ 발송 방식 │ 프리셋 │ 상태 │ 발송 │ │ +│ ├────┼──────────┼──────────┼────────┼───────┼───────┤ │ +│ │ 1 │ 주일블라인드│ 자체 SMTP│ Gmail │ ✅ 활성│ 23건 │ │ +│ │ 2 │ 경동스크린 │ SAM 기본 │ — │ ✅ 활성│ 12건 │ │ +│ │ 3 │ 테스트업체 │ 미설정 │ — │ ⚠️ 미설정│ 0건 │ │ +│ └────┴──────────┴──────────┴────────┴───────┴───────┘ │ +│ │ +│ ⚠️ 미설정 테넌트는 플랫폼 기본 SMTP로 발송됩니다 │ +└──────────────────────────────────────────────────────────┘ +``` + +### 5.4 파일 구조 + +``` +mng/ +├── app/Http/Controllers/System/ +│ └── TenantMailConfigController.php ← 컨트롤러 +├── app/Services/Mail/ +│ ├── TenantMailService.php ← 메일 발송 서비스 +│ └── SmtpConnectionTester.php ← SMTP 연결 테스트 +├── config/ +│ └── mail-presets.php ← SMTP 프리셋 설정 +└── resources/views/system/tenant-mail/ + ├── index.blade.php ← 테넌트 목록 + └── edit.blade.php ← 설정 폼 +``` + +--- + +## 6. SMTP 연결 테스트 + +### 6.1 테스트 절차 + +``` +[🔧 연결 테스트] 클릭 + │ + ├── 1. SMTP 서버 접속 (TCP 연결) + │ └── 실패: "SMTP 서버에 접속할 수 없습니다 (호스트/포트 확인)" + │ + ├── 2. TLS/SSL 핸드셰이크 + │ └── 실패: "암호화 연결 실패 (암호화 방식 확인)" + │ + ├── 3. 인증 (AUTH LOGIN) + │ └── 실패: "인증 실패 (사용자명/비밀번호 확인)" + │ + ├── 4. 테스트 메일 발송 (선택) + │ ├── 수신자: 입력한 from_address (자기 자신에게) + │ ├── 제목: "[SAM] SMTP 연결 테스트" + │ └── 본문: "이 메일은 SAM 시스템의 SMTP 연결 테스트입니다." + │ + └── 5. 결과 반환 + ├── 응답시간 (ms) + ├── 서버 배너 (SMTP EHLO 응답) + └── 성공/실패 메시지 +``` + +### 6.2 `SmtpConnectionTester` 서비스 + +> **위치**: `mng/app/Services/Mail/SmtpConnectionTester.php` + +```php +class SmtpConnectionTester +{ + /** + * SMTP 연결 테스트 실행 + * + * @return array{ + * success: bool, + * message: string, + * response_time_ms: int, + * server_banner: string|null, + * error_code: string|null + * } + */ + public function test( + string $host, + int $port, + string $encryption, + string $username, + string $password, + ?string $testRecipient = null + ): array; +} +``` + +### 6.3 에러 코드 및 트러블슈팅 + +| 에러 코드 | 메시지 | 원인 | 해결 방법 | +|----------|--------|------|----------| +| `CONN_REFUSED` | SMTP 서버 접속 거부 | 호스트/포트 오류, 방화벽 | 호스트/포트 확인, 프리셋 재선택 | +| `TLS_FAILED` | TLS 핸드셰이크 실패 | 암호화 방식 불일치 | SSL ↔ TLS 전환 시도 | +| `AUTH_FAILED` | 인증 실패 | 비밀번호 오류, 앱 비밀번호 미사용 | 앱 비밀번호 재생성 안내 | +| `SMTP_DISABLED` | SMTP 비활성화 | 메일 설정에서 SMTP 미허용 | 메일 서비스 SMTP 활성화 안내 | +| `QUOTA_ERROR` | 발송 한도 초과 | 일일 한도 도달 | 시간 경과 후 재시도 | +| `TIMEOUT` | 연결 시간 초과 (10초) | 네트워크, 서버 과부하 | 잠시 후 재시도 | + +--- + +## 7. 테넌트 사전 준비 안내 + +### 7.1 안내 절차 + +본사 관리자가 테넌트에게 메일 연동을 위한 사전 준비를 안내한다. + +``` +본사 → 테넌트 안내 흐름: + │ + ├── 1. 테넌트가 사용하는 메일 서비스 확인 + │ "현재 회사 메일이 Gmail인가요, 네이버인가요?" + │ + ├── 2. 해당 서비스의 앱 비밀번호 생성 가이드 전달 + │ (SAM 도움말 페이지 링크 또는 PDF) + │ + ├── 3. 테넌트가 앱 비밀번호 생성 후 본사에 전달 + │ ├── SMTP용 이메일 주소 + │ └── 앱 비밀번호 (일반 비밀번호 아님) + │ + └── 4. 본사가 MNG에서 설정 → 연결 테스트 → 완료 통보 +``` + +### 7.2 제공자별 앱 비밀번호 생성 안내 + +#### Gmail / Google Workspace + +``` +1. Google 계정 > 보안 > 2단계 인증 활성화 (필수 전제) +2. Google 계정 > 보안 > 앱 비밀번호 +3. "앱 선택" → "기타 (맞춤 이름)" → "SAM" 입력 +4. 생성된 16자리 비밀번호를 본사에 전달 +``` + +#### 네이버 메일 + +``` +1. 네이버 메일 > 환경설정 > POP3/IMAP 설정 +2. "IMAP/SMTP 사용" → "사용함" 체크 +3. 네이버 계정 > 보안 설정 > 2단계 인증 활성화 +4. "앱 비밀번호 관리" → 앱 이름 "SAM" 입력 → 생성 +5. 생성된 비밀번호를 본사에 전달 +``` + +#### 네이버웍스 + +``` +1. 네이버웍스 관리자 콘솔 접속 (관리자 권한 필요) +2. 보안 > 외부 앱 연동 > SMTP 허용 +3. 사용할 계정의 이메일/비밀번호를 본사에 전달 + (또는 앱 전용 비밀번호 생성) +``` + +#### 다음/카카오 메일 + +``` +1. 다음 메일 > 환경설정 > IMAP/POP > SMTP 사용 체크 +2. 카카오 계정 > 보안 > 앱 비밀번호 생성 +3. 생성된 비밀번호를 본사에 전달 +``` + +#### Microsoft 365 + +``` +1. Microsoft 365 관리자 센터 > 인증 정책 +2. SMTP AUTH 허용 (테넌트 수준 또는 사용자 수준) +3. 해당 사용자 계정의 이메일/비밀번호 전달 + ※ Basic Auth 폐지 추세 → OAuth2 연동(Phase 3) 권장 +``` + +#### 카페24 / 가비아 호스팅 메일 + +``` +1. 호스팅 관리 패널에서 SMTP 서버 주소 확인 +2. 메일 계정의 이메일/비밀번호를 본사에 전달 + (호스팅 메일은 별도 앱 비밀번호 없이 일반 비밀번호 사용) +``` + +--- + +## 8. `tenant_mail_configs` 확장 — `options` JSON + +> 기존 [이메일 발송 정책](../standards/email-policy.md)의 `options` 구조에 연동 관련 필드를 추가한다. + +### 8.1 전체 `options` JSON 구조 + +```json +{ + "smtp": { + "host": "smtp.gmail.com", + "port": 587, + "username": "admin@juil-blind.co.kr", + "password": "", + "encryption": "tls" + }, + "preset": "gmail", + "branding": { + "logo_url": "/storage/tenants/1/logo.png", + "primary_color": "#1a56db", + "company_name": "주일블라인드", + "company_address": "서울시 강남구...", + "company_phone": "02-1234-5678", + "footer_text": "본 메일은 SAM 시스템에서 발송되었습니다." + }, + "connection_test": { + "last_tested_at": "2026-03-11T14:30:00", + "last_result": "success", + "response_time_ms": 230, + "server_banner": "220 smtp.gmail.com ESMTP", + "tested_by": "admin@codebridge-x.com" + }, + "domain_auth": { + "spf_verified": false, + "dkim_verified": false, + "dkim_selector": null, + "verified_at": null + }, + "oauth": { + "provider": null, + "access_token": null, + "refresh_token": null, + "expires_at": null + } +} +``` + +### 8.2 `options` 키 설명 + +| 키 | Phase | 설명 | +|-----|-------|------| +| `smtp.*` | Phase 2 | 테넌트 SMTP 접속 정보 | +| `preset` | Phase 2 | 프리셋 식별자 (`gmail`, `naver`, `custom` 등) | +| `branding.*` | Phase 1~2 | 메일 템플릿 브랜딩 정보 | +| `connection_test.*` | Phase 2 | 마지막 SMTP 연결 테스트 결과 | +| `domain_auth.*` | Phase 2+ | SPF/DKIM 도메인 인증 상태 | +| `oauth.*` | Phase 3 | OAuth2 토큰 정보 (향후) | + +--- + +## 9. 도메인 인증 (SPF/DKIM) — Phase 2+ + +### 9.1 필요성 + +Phase 1(플랫폼 발송)에서 테넌트 도메인이 아닌 SAM 도메인으로 발송하면 스팸 분류될 수 있다. +도메인 인증을 통해 SAM이 테넌트 도메인 대신 발송하는 것을 허용한다. + +### 9.2 인증 항목 + +| 인증 | 역할 | 테넌트 조치 | +|------|------|------------| +| **SPF** | "이 IP에서 보내는 메일은 정상" | 테넌트 DNS에 SAM 발송 IP 추가 | +| **DKIM** | 메일 위변조 방지 서명 | SAM이 생성한 TXT 레코드를 DNS에 등록 | +| **DMARC** | SPF+DKIM 정책 정의 | 선택 사항 (권장) | + +### 9.3 도메인 인증 흐름 (MNG 마법사) + +``` +본사 관리자가 MNG에서 도메인 인증 진행 + │ + ├── 1. 테넌트 도메인 입력 (예: juil-blind.co.kr) + │ + ├── 2. SAM이 DNS 레코드 자동 생성 + │ ├── SPF: "v=spf1 include:sam.codebridge-x.com ~all" + │ └── DKIM: sam2026._domainkey.juil-blind.co.kr → 공개키 + │ + ├── 3. 테넌트 IT 담당자에게 DNS 레코드 추가 요청 + │ (안내 메일 또는 텍스트로 전달) + │ + ├── 4. [인증 확인] 버튼 → DNS 조회로 레코드 확인 + │ ├── 성공: domain_auth.spf_verified = true + │ └── 실패: "DNS 전파 대기 중 (최대 48시간)" + │ + └── 5. 인증 완료 시 Phase 1에서도 높은 도달률 확보 +``` + +> **현실적 판단**: 대부분의 중소기업 테넌트는 DNS 관리 역량이 없으므로, Phase 2(SMTP 릴레이)를 기본으로 권장하고 도메인 인증은 선택 사항으로 제공한다. + +--- + +## 10. API 엔드포인트 + +### 10.1 MNG 내부 라우트 + +| Method | Path | 설명 | +|--------|------|------| +| `GET` | `/system/tenants/{tenant}/mail-config` | 메일 설정 폼 화면 | +| `PUT` | `/system/tenants/{tenant}/mail-config` | 메일 설정 저장 | +| `POST` | `/system/tenants/{tenant}/mail-config/test` | SMTP 연결 테스트 | +| `GET` | `/system/tenants/mail-config/presets` | 프리셋 목록 JSON | +| `GET` | `/system/tenants/mail-config/overview` | 전체 테넌트 설정 현황 | + +### 10.2 연결 테스트 API + +**Request**: + +```json +POST /system/tenants/1/mail-config/test +{ + "provider": "smtp", + "preset": "gmail", + "host": "smtp.gmail.com", + "port": 587, + "encryption": "tls", + "username": "admin@juil-blind.co.kr", + "password": "xxxx xxxx xxxx xxxx", + "send_test_mail": true +} +``` + +**Response (성공)**: + +```json +{ + "success": true, + "message": "SMTP 연결 성공", + "data": { + "response_time_ms": 230, + "server_banner": "220 smtp.gmail.com ESMTP", + "test_mail_sent": true, + "test_mail_to": "admin@juil-blind.co.kr" + } +} +``` + +**Response (실패)**: + +```json +{ + "success": false, + "message": "인증 실패 — 앱 비밀번호를 확인하세요", + "data": { + "error_code": "AUTH_FAILED", + "troubleshoot": "Gmail은 앱 비밀번호가 필요합니다. 2단계 인증 활성화 후 앱 비밀번호를 생성하세요.", + "guide_url": "/docs/email-setup/gmail" + } +} +``` + +--- + +## 11. 보안 규칙 + +### 11.1 필수 준수 사항 + +``` +✅ SMTP 비밀번호는 encrypt()로 암호화 저장 (DB에 평문 금지) +✅ OAuth2 토큰도 encrypt()로 암호화 저장 +✅ 연결 테스트 시 비밀번호는 HTTPS 전송만 허용 +✅ MNG 관리자만 테넌트 메일 설정 접근 가능 (권한: system.tenant.mail) +✅ SMTP 비밀번호는 API 응답/뷰에서 마스킹 처리 (••••••••) +✅ 연결 테스트 결과는 mail_logs가 아닌 options.connection_test에 기록 +``` + +### 11.2 금지 사항 + +``` +❌ 테넌트가 직접 MNG에서 메일 설정 변경 금지 (본사만 가능) +❌ SMTP 비밀번호를 로그/콘솔에 출력 금지 +❌ 암호화 없이 비밀번호 저장 금지 +❌ 다른 테넌트의 SMTP 설정 조회 금지 (TenantScope 적용) +``` + +### 11.3 비밀번호 수명주기 + +``` +비밀번호 입력 → encrypt() 암호화 → DB 저장 + │ +연결 테스트 시 ← decrypt() 복호화 ────┘ +메일 발송 시 ← decrypt() 복호화 ────┘ +설정 폼 표시 → "••••••••" 마스킹 (원본 미노출) +``` + +--- + +## 12. 구현 체크리스트 + +### Phase 1: 플랫폼 발송 (기반) + +- [ ] `tenant_mail_configs` 마이그레이션 (API) +- [ ] `TenantMailConfig` 모델 생성 (API + MNG) +- [ ] `TenantMailService` 생성 (MNG) +- [ ] MNG 관리 화면 — 기본 설정 폼 (from_name, reply_to) +- [ ] 기존 Mailable을 `TenantMailService` 경유로 전환 + +### Phase 2: SMTP 릴레이 + +- [ ] `config/mail-presets.php` 프리셋 설정 파일 생성 +- [ ] MNG 관리 화면 — 프리셋 선택 + SMTP 설정 폼 +- [ ] `SmtpConnectionTester` 서비스 구현 +- [ ] SMTP 연결 테스트 API 구현 +- [ ] 프리셋 선택 시 호스트/포트/암호화 자동 채움 (HTMX) +- [ ] SMTP 비밀번호 encrypt/decrypt 처리 +- [ ] 테넌트별 앱 비밀번호 생성 가이드 페이지 + +### Phase 3: OAuth2 연동 (향후) + +- [ ] Google OAuth2 연동 (Gmail API `gmail.send` scope) +- [ ] Microsoft Graph OAuth2 연동 (`Mail.Send` scope) +- [ ] OAuth 토큰 자동 갱신 (Refresh Token) +- [ ] MNG에서 "Google 연결" / "Microsoft 연결" 버튼 + +--- + +## 관련 문서 + +- [이메일 발송 정책](../standards/email-policy.md) — 내부 발송 아키텍처, 테이블 설계, 서비스 설계 +- [테넌트 DB 구조](../../system/database/tenants.md) — 테넌트 테이블, 데이터 격리 +- [options JSON 컬럼 정책](../standards/options-column-policy.md) — options 컬럼 사용 규칙 +- [전자서명 기능](../../features/esign/README.md) — 메일 발송이 많은 주요 기능 + +--- + +**최종 업데이트**: 2026-03-11 diff --git a/dev/standards/email-policy.md b/dev/standards/email-policy.md index 30a2737..d3a2804 100644 --- a/dev/standards/email-policy.md +++ b/dev/standards/email-policy.md @@ -400,6 +400,7 @@ app(TenantMailService::class)->send( ## 관련 문서 +- [테넌트 이메일 연동 가이드](../guides/tenant-email-integration-guide.md) — MNG에서 테넌트 메일 설정, SMTP 프리셋, 연결 테스트 - [테넌트 DB 구조](../../system/database/tenants.md) - [전자서명 기능](../../features/esign/README.md) - [급여관리 기능](../../features/finance/payroll.md)