Просмотр исходного кода

add spec for oauth2-emil-support

Yuki Takei 2 месяцев назад
Родитель
Сommit
a79065546d

+ 967 - 0
.kiro/specs/oauth2-email-support/design.md

@@ -0,0 +1,967 @@
+# OAuth 2.0 Email Support - Technical Design
+
+## Overview
+
+This feature adds OAuth 2.0 authentication support for sending emails through Google Workspace accounts in GROWI. Administrators can configure email transmission using OAuth 2.0 credentials (Client ID, Client Secret, Refresh Token) instead of traditional SMTP passwords. This integration extends the existing mail service architecture while maintaining full backward compatibility with SMTP and SES configurations.
+
+**Purpose**: Enable secure, token-based email authentication for Google Workspace accounts, improving security by eliminating password-based SMTP authentication and following Google's recommended practices for application email integration.
+
+**Users**: GROWI administrators configuring email transmission settings will use the new OAuth 2.0 option alongside existing SMTP and SES methods.
+
+**Impact**: Extends the mail service to support a third transmission method (oauth2) without modifying existing SMTP or SES functionality. No breaking changes to existing deployments.
+
+### Goals
+
+- Add OAuth 2.0 as a transmission method option in mail settings
+- Support Google Workspace email sending via Gmail API with OAuth 2.0 credentials
+- Maintain backward compatibility with existing SMTP and SES configurations
+- Provide consistent admin UI experience following SMTP/SES patterns
+- Implement automatic OAuth 2.0 token refresh using nodemailer's built-in support
+- Ensure secure storage and handling of OAuth 2.0 credentials
+
+### Non-Goals
+
+- OAuth 2.0 providers beyond Google Workspace (Microsoft 365, generic OAuth 2.0 servers)
+- Migration tool from SMTP to OAuth 2.0 (administrators manually reconfigure)
+- Authorization flow UI for obtaining refresh tokens (documented external process via Google Cloud Console)
+- Multi-account or account rotation support (single OAuth 2.0 account per instance)
+- Email queuing or rate limiting specific to OAuth 2.0 (relies on existing mail service behavior)
+
+## Architecture
+
+### Existing Architecture Analysis
+
+**Current Mail Service Implementation**:
+- **Service Location**: `apps/app/src/server/service/mail.ts` (MailService class)
+- **Initialization**: MailService instantiated from Crowi container, loaded on app startup
+- **Transmission Methods**: Currently supports 'smtp' and 'ses' via `mail:transmissionMethod` config
+- **Factory Pattern**: `createSMTPClient()` and `createSESClient()` create nodemailer transports
+- **Configuration**: ConfigManager loads settings from MongoDB via `mail:*` namespace keys
+- **S2S Messaging**: Supports distributed config updates via `mailServiceUpdated` events
+- **Test Email**: SMTP-only test email functionality in admin UI
+
+**Current Admin UI Structure**:
+- **Main Component**: `MailSetting.tsx` - form container with transmission method radio buttons
+- **Sub-Components**: `SmtpSetting.tsx`, `SesSetting.tsx` - conditional rendering based on selected method
+- **State Management**: AdminAppContainer (unstated) manages form state and API calls
+- **Form Library**: react-hook-form for validation and submission
+- **API Integration**: `updateMailSettingHandler()` saves all mail settings via REST API
+
+**Integration Points**:
+- Config definition in `config-definition.ts` (add OAuth 2.0 keys)
+- MailService initialize() method (add OAuth 2.0 branch)
+- MailSetting.tsx transmission method array (add 'oauth2' option)
+- AdminAppContainer state methods (add OAuth 2.0 credential methods)
+
+### Architecture Pattern & Boundary Map
+
+```mermaid
+graph TB
+    subgraph "Client Layer"
+        MailSettingUI[MailSetting Component]
+        OAuth2SettingUI[OAuth2Setting Component]
+        SmtpSettingUI[SmtpSetting Component]
+        SesSettingUI[SesSetting Component]
+        AdminContainer[AdminAppContainer]
+    end
+
+    subgraph "API Layer"
+        AppSettingsAPI[App Settings API]
+        MailTestAPI[Mail Test API]
+    end
+
+    subgraph "Service Layer"
+        MailService[MailService]
+        ConfigManager[ConfigManager]
+        S2SMessaging[S2S Messaging]
+    end
+
+    subgraph "External Services"
+        GoogleOAuth[Google OAuth 2.0 API]
+        GmailAPI[Gmail API]
+        SMTPServer[SMTP Server]
+        SESAPI[AWS SES API]
+    end
+
+    subgraph "Data Layer"
+        MongoDB[(MongoDB Config)]
+    end
+
+    MailSettingUI --> AdminContainer
+    OAuth2SettingUI --> AdminContainer
+    SmtpSettingUI --> AdminContainer
+    SesSettingUI --> AdminContainer
+
+    AdminContainer --> AppSettingsAPI
+    AdminContainer --> MailTestAPI
+
+    AppSettingsAPI --> ConfigManager
+    MailTestAPI --> MailService
+
+    MailService --> ConfigManager
+    MailService --> S2SMessaging
+
+    ConfigManager --> MongoDB
+
+    MailService --> GoogleOAuth
+    MailService --> GmailAPI
+    MailService --> SMTPServer
+    MailService --> SESAPI
+
+    S2SMessaging -.->|mailServiceUpdated| MailService
+```
+
+**Architecture Integration**:
+- **Selected Pattern**: Factory Method Extension - adds `createOAuth2Client()` to existing MailService factory methods
+- **Domain Boundaries**:
+  - **Client**: Admin UI components for OAuth 2.0 configuration (follows existing SmtpSetting/SesSetting pattern)
+  - **Service**: MailService handles all transmission methods; OAuth 2.0 isolated in new factory method
+  - **Config**: ConfigManager persists OAuth 2.0 credentials using `mail:oauth2*` namespace
+  - **External**: Google OAuth 2.0 API for token management; Gmail API for email transmission
+- **Existing Patterns Preserved**:
+  - Transmission method selection pattern (radio buttons, conditional rendering)
+  - Factory method pattern for transport creation
+  - Config namespace pattern (`mail:*` keys)
+  - Unstated container state management
+  - S2S messaging for distributed config updates
+- **New Components Rationale**:
+  - **OAuth2Setting Component**: Maintains UI consistency with SMTP/SES; enables modular development
+  - **createOAuth2Client() Method**: Isolates OAuth 2.0 transport logic; follows existing factory pattern
+  - **Four Config Keys**: Minimal set for OAuth 2.0 (user, clientId, clientSecret, refreshToken)
+- **Steering Compliance**:
+  - Feature-based organization (mail service domain)
+  - Named exports throughout
+  - Type safety with explicit TypeScript interfaces
+  - Immutable config updates
+  - Security-first credential handling
+
+### Technology Stack
+
+| Layer | Choice / Version | Role in Feature | Notes |
+|-------|------------------|-----------------|-------|
+| Frontend | React 18.x + TypeScript | OAuth2Setting UI component | Existing stack, no new dependencies |
+| Frontend | react-hook-form | Form validation and state | Existing dependency, consistent with SmtpSetting/SesSetting |
+| Backend | Node.js + TypeScript | MailService OAuth 2.0 integration | Existing runtime, no version changes |
+| Backend | nodemailer 6.x | OAuth 2.0 transport creation | Existing dependency with built-in OAuth 2.0 support |
+| Data | MongoDB | Config storage for OAuth 2.0 credentials | Existing database, new config keys only |
+| External | Google OAuth 2.0 API | Token refresh endpoint | Standard Google API, https://oauth2.googleapis.com/token |
+| External | Gmail API | Email transmission via OAuth 2.0 | Accessed via nodemailer Gmail transport |
+
+**Key Technology Decisions**:
+- **Nodemailer OAuth 2.0**: Built-in support eliminates need for additional OAuth 2.0 libraries; automatic token refresh reduces complexity
+- **No New Dependencies**: Feature fully implemented with existing packages; zero dependency risk
+- **MongoDB Encryption**: Credentials stored using existing ConfigManager encryption (same as SMTP passwords)
+- **Gmail Service Shortcut**: Nodemailer's `service: "gmail"` simplifies configuration and handles Gmail API specifics
+
+## System Flows
+
+### OAuth 2.0 Configuration Flow
+
+```mermaid
+sequenceDiagram
+    participant Admin as Administrator
+    participant UI as MailSetting UI
+    participant Container as AdminAppContainer
+    participant API as App Settings API
+    participant Config as ConfigManager
+    participant DB as MongoDB
+
+    Admin->>UI: Select "oauth2" transmission method
+    UI->>UI: Render OAuth2Setting component
+    Admin->>UI: Enter OAuth 2.0 credentials
+    Admin->>UI: Click Update button
+    UI->>Container: handleSubmit formData
+    Container->>API: POST app-settings
+    API->>API: Validate OAuth 2.0 fields
+    alt Validation fails
+        API-->>Container: 400 Bad Request
+        Container-->>UI: Display error toast
+    else Validation passes
+        API->>Config: setConfig mail:oauth2*
+        Config->>DB: Save encrypted credentials
+        DB-->>Config: Success
+        Config-->>API: Success
+        API-->>Container: 200 OK
+        Container-->>UI: Display success toast
+    end
+```
+
+### Email Sending with OAuth 2.0 Flow
+
+```mermaid
+sequenceDiagram
+    participant App as GROWI Application
+    participant Mail as MailService
+    participant Nodemailer as Nodemailer Transport
+    participant Google as Google OAuth 2.0 API
+    participant Gmail as Gmail API
+
+    App->>Mail: send emailConfig
+    Mail->>Mail: Check mailer setup
+    alt Mailer not setup
+        Mail-->>App: Error Mailer not set up
+    else Mailer setup oauth2
+        Mail->>Nodemailer: sendMail mailConfig
+        Nodemailer->>Nodemailer: Check access token validity
+        alt Access token expired
+            Nodemailer->>Google: POST token refresh
+            Google-->>Nodemailer: New access token
+            Nodemailer->>Nodemailer: Cache access token
+        end
+        Nodemailer->>Gmail: POST send message
+        alt Authentication failure
+            Gmail-->>Nodemailer: 401 Unauthorized
+            Nodemailer-->>Mail: Error Invalid credentials
+            Mail-->>App: Error with OAuth 2.0 details
+        else Success
+            Gmail-->>Nodemailer: 200 OK message ID
+            Nodemailer-->>Mail: Success
+            Mail->>Mail: Log transmission success
+            Mail-->>App: Email sent successfully
+        end
+    end
+```
+
+**Flow-Level Decisions**:
+- **Token Refresh**: Handled entirely by nodemailer; MailService does not implement custom refresh logic
+- **Error Handling**: OAuth 2.0 errors logged with specific Google API error codes for admin troubleshooting
+- **Credential Validation**: Performed at API layer before persisting to database; prevents invalid config states
+- **S2S Sync**: OAuth 2.0 config changes trigger `mailServiceUpdated` event for distributed deployments (existing pattern)
+
+## Requirements Traceability
+
+| Requirement | Summary | Components | Interfaces | Flows |
+|-------------|---------|------------|------------|-------|
+| 1.1 | Add OAuth 2.0 transmission method option | MailSetting.tsx, config-definition.ts | ConfigDefinition | Configuration |
+| 1.2 | Display OAuth 2.0 config fields when selected | OAuth2Setting.tsx, MailSetting.tsx | React Props | Configuration |
+| 1.3 | Validate email address format | AdminAppContainer, App Settings API | API Contract | Configuration |
+| 1.4 | Validate non-empty OAuth 2.0 credentials | AdminAppContainer, App Settings API | API Contract | Configuration |
+| 1.5 | Securely store OAuth 2.0 credentials with encryption | ConfigManager, MongoDB | Data Model | Configuration |
+| 1.6 | Confirm successful configuration save | AdminAppContainer, MailSetting.tsx | API Contract | Configuration |
+| 1.7 | Display descriptive error messages on save failure | AdminAppContainer, MailSetting.tsx | API Contract | Configuration |
+| 2.1 | Use nodemailer Gmail OAuth 2.0 transport | MailService.createOAuth2Client() | Service Interface | Email Sending |
+| 2.2 | Authenticate to Gmail API with OAuth 2.0 | MailService.createOAuth2Client() | External API | Email Sending |
+| 2.3 | Set FROM address to configured email | MailService.setupMailConfig() | Service Interface | Email Sending |
+| 2.4 | Log successful email transmission | MailService.send() | Service Interface | Email Sending |
+| 2.5 | Support all email content types | MailService.send() (existing) | Service Interface | Email Sending |
+| 2.6 | Process email queue sequentially | MailService.send() (existing) | Service Interface | Email Sending |
+| 3.1 | Use nodemailer automatic token refresh | Nodemailer OAuth 2.0 transport | External Library | Email Sending |
+| 3.2 | Request new access token with refresh token | Nodemailer OAuth 2.0 transport | External API | Email Sending |
+| 3.3 | Continue email sending after token refresh | Nodemailer OAuth 2.0 transport | External Library | Email Sending |
+| 3.4 | Log error and notify admin on refresh failure | MailService.send(), Error Handler | Service Interface | Email Sending |
+| 3.5 | Cache access tokens in memory | Nodemailer OAuth 2.0 transport | External Library | Email Sending |
+| 3.6 | Invalidate cached tokens on config update | MailService.initialize() | Service Interface | Configuration |
+| 4.1 | Display OAuth 2.0 form with consistent styling | OAuth2Setting.tsx | React Component | Configuration |
+| 4.2 | Preserve OAuth 2.0 credentials when switching methods | AdminAppContainer state | State Management | Configuration |
+| 4.3 | Provide field-level help text | OAuth2Setting.tsx | React Component | Configuration |
+| 4.4 | Mask sensitive fields (last 4 characters) | OAuth2Setting.tsx | React Component | Configuration |
+| 4.5 | Provide test email button | MailSetting.tsx | API Contract | Email Sending |
+| 4.6 | Display test email result with detailed errors | AdminAppContainer, MailSetting.tsx | API Contract | Email Sending |
+| 5.1 | Log specific OAuth 2.0 error codes | MailService error handler | Service Interface | Email Sending |
+| 5.2 | Retry email sending with exponential backoff | MailService.send() | Service Interface | Email Sending |
+| 5.3 | Store failed emails after all retries | MailService.send() | Service Interface | Email Sending |
+| 5.4 | Never log credentials in plain text | MailService, ConfigManager | Security Pattern | All flows |
+| 5.5 | Require admin authentication for config page | App Settings API | API Contract | Configuration |
+| 5.6 | Stop OAuth 2.0 sending when credentials deleted | MailService.initialize() | Service Interface | Email Sending |
+| 5.7 | Validate SSL/TLS for OAuth 2.0 endpoints | Nodemailer OAuth 2.0 transport | External Library | Email Sending |
+| 6.1 | Maintain backward compatibility with SMTP/SES | MailService, config-definition.ts | All Interfaces | All flows |
+| 6.2 | Use only active transmission method | MailService.initialize() | Service Interface | Email Sending |
+| 6.3 | Allow switching transmission methods without data loss | AdminAppContainer, ConfigManager | State Management | Configuration |
+| 6.4 | Display configuration error if no method set | MailService, MailSetting.tsx | Service Interface | Configuration |
+| 6.5 | Expose OAuth 2.0 status via admin API | App Settings API | API Contract | Configuration |
+
+## Components and Interfaces
+
+### Component Summary
+
+| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
+|-----------|--------------|--------|--------------|--------------------------|-----------|
+| MailService | Server/Service | Email transmission with OAuth 2.0 support | 2.1-2.6, 3.1-3.6, 5.1-5.7, 6.2, 6.4 | ConfigManager (P0), Nodemailer (P0), S2SMessaging (P1) | Service |
+| OAuth2Setting | Client/UI | OAuth 2.0 credential input form | 1.2, 4.1, 4.3, 4.4 | AdminAppContainer (P0), react-hook-form (P0) | State |
+| AdminAppContainer | Client/State | State management for mail settings | 1.3, 1.4, 1.6, 1.7, 4.2, 6.3 | App Settings API (P0) | API |
+| ConfigManager | Server/Service | Persist OAuth 2.0 credentials | 1.5, 6.1, 6.3 | MongoDB (P0) | Service, State |
+| App Settings API | Server/API | Mail settings CRUD operations | 1.3-1.7, 4.5-4.6, 5.5, 6.5 | ConfigManager (P0), MailService (P1) | API |
+| Config Definition | Server/Config | OAuth 2.0 config schema | 1.1, 6.1 | None | State |
+
+### Server / Service Layer
+
+#### MailService
+
+| Field | Detail |
+|-------|--------|
+| Intent | Extend email transmission service with OAuth 2.0 support using Gmail API |
+| Requirements | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 5.1, 5.2, 5.3, 5.4, 5.6, 5.7, 6.2, 6.4 |
+| Owner / Reviewers | Backend team |
+
+**Responsibilities & Constraints**
+- Create OAuth 2.0 nodemailer transport using Gmail service with credentials from ConfigManager
+- Handle OAuth 2.0 authentication failures and token refresh errors with specific error logging
+- Implement retry logic with exponential backoff (1s, 2s, 4s) for transient failures
+- Store failed emails after all retry attempts for manual review
+- Maintain single active transmission method (smtp, ses, or oauth2) per instance
+- Invalidate cached OAuth 2.0 tokens when configuration changes via S2S messaging
+
+**Dependencies**
+- Inbound: Crowi container — service initialization (P0)
+- Inbound: Application modules — email sending requests (P0)
+- Inbound: S2S Messaging — config update notifications (P1)
+- Outbound: ConfigManager — load OAuth 2.0 credentials (P0)
+- Outbound: Nodemailer — create transport and send emails (P0)
+- External: Google OAuth 2.0 API — token refresh (P0)
+- External: Gmail API — email transmission (P0)
+
+**Contracts**: Service [x]
+
+##### Service Interface
+
+```typescript
+interface MailServiceOAuth2Extension {
+  /**
+   * Create OAuth 2.0 nodemailer transport for Gmail
+   */
+  createOAuth2Client(option?: OAuth2TransportOptions): Transporter | null;
+
+  /**
+   * Send email with retry logic and error handling
+   */
+  sendWithRetry(config: EmailConfig, maxRetries?: number): Promise<SendResult>;
+
+  /**
+   * Store failed email for manual review
+   */
+  storeFailedEmail(config: EmailConfig, error: Error): Promise<void>;
+
+  /**
+   * Wait with exponential backoff
+   */
+  exponentialBackoff(attempt: number): Promise<void>;
+}
+
+interface OAuth2TransportOptions {
+  user: string;
+  clientId: string;
+  clientSecret: string;
+  refreshToken: string;
+}
+
+interface MailService {
+  send(config: EmailConfig): Promise<void>;
+  initialize(): void;
+  isMailerSetup: boolean;
+}
+
+interface EmailConfig {
+  to: string;
+  from?: string;
+  subject?: string;
+  template: string;
+  vars?: Record<string, unknown>;
+}
+
+interface SendResult {
+  messageId: string;
+  response: string;
+  envelope: {
+    from: string;
+    to: string[];
+  };
+}
+```
+
+- **Preconditions**:
+  - ConfigManager loaded with valid `mail:oauth2*` configuration values
+  - Nodemailer package version supports OAuth 2.0 (v6.x+)
+  - Google OAuth 2.0 refresh token has `https://mail.google.com/` scope
+
+- **Postconditions**:
+  - OAuth 2.0 transport created with automatic token refresh enabled
+  - `isMailerSetup` flag set to true when transport successfully created
+  - Failed transport creation returns null and logs error
+  - Successful email sends logged with messageId and recipient
+  - Failed emails stored after retry exhaustion
+
+- **Invariants**:
+  - Only one transmission method active at a time
+  - Credentials never logged in plain text
+  - Token refresh handled transparently by nodemailer
+  - Retry backoff: 1s, 2s, 4s
+
+**Implementation Notes**
+- **Integration**: Add OAuth 2.0 branch to initialize() method
+- **Validation**: createOAuth2Client() validates all four credentials present
+- **Error Handling**:
+  - Extract Google API error codes (invalid_grant, insufficient_permission)
+  - Log context: error, code, user, clientId (last 4 chars), timestamp
+  - Implement sendWithRetry() wrapper with exponential backoff
+  - Store failed emails in MongoDB failedEmails collection
+- **Token Refresh**: Nodemailer handles refresh automatically
+- **Encryption**: Credentials loaded from ConfigManager (handles decryption)
+- **Testing**: Mock nodemailer OAuth 2.0 transport; test invalid credentials, expired tokens, network failures, retry logic
+- **Risks**: Google rate limiting (mitigated by backoff), refresh token revocation (logged for admin action)
+
+#### ConfigManager
+
+| Field | Detail |
+|-------|--------|
+| Intent | Persist and retrieve OAuth 2.0 credentials with encryption |
+| Requirements | 1.5, 6.1, 6.3 |
+
+**Responsibilities & Constraints**
+- Store four new OAuth 2.0 config keys with encryption
+- Support transmission method value 'oauth2'
+- Maintain all SMTP and SES config values when OAuth 2.0 is configured
+
+**Dependencies**
+- Inbound: MailService, App Settings API (P0)
+- Outbound: MongoDB, Encryption Service (P0)
+
+**Contracts**: Service [x] / State [x]
+
+##### Service Interface
+
+```typescript
+interface ConfigManagerOAuth2Extension {
+  getConfig(key: 'mail:oauth2User'): string | undefined;
+  getConfig(key: 'mail:oauth2ClientId'): string | undefined;
+  getConfig(key: 'mail:oauth2ClientSecret'): string | undefined;
+  getConfig(key: 'mail:oauth2RefreshToken'): string | undefined;
+  getConfig(key: 'mail:transmissionMethod'): 'smtp' | 'ses' | 'oauth2' | undefined;
+
+  setConfig(key: 'mail:oauth2User', value: string): Promise<void>;
+  setConfig(key: 'mail:oauth2ClientId', value: string): Promise<void>;
+  setConfig(key: 'mail:oauth2ClientSecret', value: string): Promise<void>;
+  setConfig(key: 'mail:oauth2RefreshToken', value: string): Promise<void>;
+  setConfig(key: 'mail:transmissionMethod', value: 'smtp' | 'ses' | 'oauth2'): Promise<void>;
+}
+```
+
+##### State Management
+
+- **State Model**: OAuth 2.0 credentials stored as separate config documents in MongoDB
+- **Persistence**: Encrypted at write time; decrypted at read time
+- **Consistency**: Atomic writes per config key
+- **Concurrency**: Last-write-wins; S2S messaging for eventual consistency
+
+**Implementation Notes**
+- Add config definitions following mail:smtp* pattern
+- Use isSecret: true for clientSecret and refreshToken
+- Define transmissionMethod as 'smtp' | 'ses' | 'oauth2' | undefined
+
+### Client / UI Layer
+
+#### OAuth2Setting Component
+
+| Field | Detail |
+|-------|--------|
+| Intent | Render OAuth 2.0 credential input form with help text and field masking |
+| Requirements | 1.2, 4.1, 4.3, 4.4 |
+
+**Responsibilities & Constraints**
+- Display four input fields with help text
+- Mask saved Client Secret and Refresh Token (show last 4 characters)
+- Follow SMTP/SES visual patterns
+- Use react-hook-form register
+
+**Dependencies**
+- Inbound: MailSetting component (P0)
+- Outbound: AdminAppContainer (P1)
+- External: react-hook-form (P0)
+
+**Contracts**: State [x]
+
+##### State Management
+
+```typescript
+interface OAuth2SettingProps {
+  register: UseFormRegister<MailSettingsFormData>;
+  adminAppContainer?: AdminAppContainer;
+}
+
+interface MailSettingsFormData {
+  fromAddress: string;
+  transmissionMethod: 'smtp' | 'ses' | 'oauth2';
+  smtpHost: string;
+  smtpPort: string;
+  smtpUser: string;
+  smtpPassword: string;
+  sesAccessKeyId: string;
+  sesSecretAccessKey: string;
+  oauth2User: string;
+  oauth2ClientId: string;
+  oauth2ClientSecret: string;
+  oauth2RefreshToken: string;
+}
+```
+
+**Implementation Notes**
+- **Help Text**: Include for all four fields
+  - oauth2User: "The email address of the authorized Google account"
+  - oauth2ClientId: "Obtain from Google Cloud Console → APIs & Services → Credentials"
+  - oauth2ClientSecret: "Found in the same OAuth 2.0 Client ID details page"
+  - oauth2RefreshToken: "The refresh token obtained from OAuth 2.0 authorization flow"
+- **Field Masking**:
+  - Display ****abcd (last 4 characters) when field not edited
+  - Clear mask on focus for full edit
+  - Applies to oauth2ClientSecret, oauth2RefreshToken
+
+#### AdminAppContainer (Extension)
+
+| Field | Detail |
+|-------|--------|
+| Intent | Manage OAuth 2.0 credential state and API interactions |
+| Requirements | 1.3, 1.4, 1.6, 1.7, 4.2, 6.3 |
+
+**Responsibilities & Constraints**
+- Add four state properties and setter methods
+- Include OAuth 2.0 credentials in API payload
+- Validate email format before API call
+- Display success/error toasts
+
+**Dependencies**
+- Inbound: MailSetting, OAuth2Setting (P0)
+- Outbound: App Settings API (P0)
+
+**Contracts**: State [x] / API [x]
+
+##### State Management
+
+```typescript
+interface AdminAppContainerOAuth2State {
+  fromAddress?: string;
+  transmissionMethod?: 'smtp' | 'ses' | 'oauth2';
+  smtpHost?: string;
+  smtpPort?: string;
+  smtpUser?: string;
+  smtpPassword?: string;
+  sesAccessKeyId?: string;
+  sesSecretAccessKey?: string;
+  isMailerSetup: boolean;
+  oauth2User?: string;
+  oauth2ClientId?: string;
+  oauth2ClientSecret?: string;
+  oauth2RefreshToken?: string;
+}
+
+interface AdminAppContainerOAuth2Methods {
+  changeOAuth2User(oauth2User: string): Promise<void>;
+  changeOAuth2ClientId(oauth2ClientId: string): Promise<void>;
+  changeOAuth2ClientSecret(oauth2ClientSecret: string): Promise<void>;
+  changeOAuth2RefreshToken(oauth2RefreshToken: string): Promise<void>;
+  updateMailSettingHandler(): Promise<void>;
+}
+```
+
+**Implementation Notes**
+- Add OAuth 2.0 state properties to constructor
+- Follow pattern of existing changeSmtpHost() methods
+- Email validation: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+- Field-specific error messages in toast
+
+### Server / API Layer
+
+#### App Settings API (Extension)
+
+| Field | Detail |
+|-------|--------|
+| Intent | Handle OAuth 2.0 credential CRUD operations with validation |
+| Requirements | 1.3, 1.4, 1.5, 1.6, 1.7, 4.5, 4.6, 5.5, 6.5 |
+
+**Responsibilities & Constraints**
+- Accept OAuth 2.0 credentials in PUT request
+- Validate email format and non-empty credentials
+- Persist via ConfigManager
+- Trigger S2S messaging
+- Require admin authentication
+
+**Dependencies**
+- Inbound: AdminAppContainer (P0)
+- Outbound: ConfigManager, MailService, S2S Messaging (P0/P1)
+
+**Contracts**: API [x]
+
+##### API Contract
+
+| Method | Endpoint | Request | Response | Errors |
+|--------|----------|---------|----------|--------|
+| PUT | /api/v3/app-settings | UpdateMailSettingsRequest | AppSettingsResponse | 400, 401, 500 |
+| GET | /api/v3/app-settings | - | AppSettingsResponse | 401, 500 |
+| POST | /api/v3/mail/send-test | - | TestEmailResponse | 400, 401, 500 |
+
+**Request/Response Schemas**:
+
+```typescript
+interface UpdateMailSettingsRequest {
+  'mail:from'?: string;
+  'mail:transmissionMethod'?: 'smtp' | 'ses' | 'oauth2';
+  'mail:smtpHost'?: string;
+  'mail:smtpPort'?: string;
+  'mail:smtpUser'?: string;
+  'mail:smtpPassword'?: string;
+  'mail:sesAccessKeyId'?: string;
+  'mail:sesSecretAccessKey'?: string;
+  'mail:oauth2User'?: string;
+  'mail:oauth2ClientId'?: string;
+  'mail:oauth2ClientSecret'?: string;
+  'mail:oauth2RefreshToken'?: string;
+}
+
+interface AppSettingsResponse {
+  appSettings: {
+    'mail:from'?: string;
+    'mail:transmissionMethod'?: 'smtp' | 'ses' | 'oauth2';
+    'mail:smtpHost'?: string;
+    'mail:smtpPort'?: string;
+    'mail:smtpUser'?: string;
+    'mail:sesAccessKeyId'?: string;
+    'mail:oauth2User'?: string;
+    'mail:oauth2ClientId'?: string;
+  };
+  isMailerSetup: boolean;
+}
+
+interface TestEmailResponse {
+  success: boolean;
+  message?: string;
+  error?: {
+    code: string;
+    message: string;
+  };
+}
+```
+
+**Validation Rules**:
+- oauth2User: Email regex /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+- oauth2ClientId: Non-empty string, max 1024 characters
+- oauth2ClientSecret: Non-empty string, max 1024 characters
+- oauth2RefreshToken: Non-empty string, max 2048 characters
+- When transmissionMethod is oauth2, all four fields required
+
+**Implementation Notes**
+- Never return oauth2ClientSecret or oauth2RefreshToken in GET response
+- Call mailService.publishUpdatedMessage() after config save
+- Support OAuth 2.0 in test email functionality
+- Field-specific validation error messages
+
+### Server / Config Layer
+
+#### Config Definition (Extension)
+
+| Field | Detail |
+|-------|--------|
+| Intent | Define OAuth 2.0 configuration schema with type safety |
+| Requirements | 1.1, 6.1 |
+
+**Config Schema**:
+
+```typescript
+const CONFIG_KEYS = [
+  'mail:oauth2User',
+  'mail:oauth2ClientId',
+  'mail:oauth2ClientSecret',
+  'mail:oauth2RefreshToken',
+];
+
+'mail:transmissionMethod': defineConfig<'smtp' | 'ses' | 'oauth2' | undefined>({
+  defaultValue: undefined,
+}),
+
+'mail:oauth2User': defineConfig<string | undefined>({
+  defaultValue: undefined,
+}),
+'mail:oauth2ClientId': defineConfig<string | undefined>({
+  defaultValue: undefined,
+}),
+'mail:oauth2ClientSecret': defineConfig<string | undefined>({
+  defaultValue: undefined,
+  isSecret: true,
+}),
+'mail:oauth2RefreshToken': defineConfig<string | undefined>({
+  defaultValue: undefined,
+  isSecret: true,
+}),
+```
+
+## Data Models
+
+### Domain Model
+
+**Mail Configuration Aggregate**:
+- **Root Entity**: MailConfiguration
+- **Value Objects**: TransmissionMethod, OAuth2Credentials, SmtpCredentials, SesCredentials
+- **Business Rules**: Only one transmission method active; OAuth2Credentials complete when all fields present
+- **Invariants**: Credentials encrypted; FROM address required
+
+### Logical Data Model
+
+**Structure Definition**:
+- **Entity**: Config (MongoDB document)
+- **Attributes**: ns, key, value, createdAt, updatedAt
+- **Natural Keys**: ns field (unique)
+
+**Consistency & Integrity**:
+- **Transaction Boundaries**: Each config key saved independently
+- **Temporal Aspects**: updatedAt tracked per entry
+
+### Physical Data Model
+
+```typescript
+interface ConfigDocument {
+  ns: string;
+  key: string;
+  value: string;
+  createdAt: Date;
+  updatedAt: Date;
+}
+
+interface FailedEmailDocument {
+  emailConfig: {
+    to: string;
+    from: string;
+    subject: string;
+    template: string;
+    vars: Record<string, unknown>;
+  };
+  error: {
+    message: string;
+    code?: string;
+    stack?: string;
+  };
+  transmissionMethod: 'smtp' | 'ses' | 'oauth2';
+  attempts: number;
+  lastAttemptAt: Date;
+  createdAt: Date;
+}
+```
+
+**Index Definitions**:
+- Config ns field (unique)
+- FailedEmail createdAt field
+
+**Encryption Strategy**:
+- AES-256 for clientSecret and refreshToken
+- Encryption key from environment variable
+
+### Data Contracts & Integration
+
+**API Data Transfer**:
+- OAuth 2.0 credentials via JSON in PUT /api/v3/app-settings
+- Client Secret and Refresh Token never returned in GET responses
+
+**Cross-Service Data Management**:
+- S2S messaging broadcasts mailServiceUpdated event
+- Eventual consistency across instances
+
+## Error Handling
+
+### Error Strategy
+
+**Retry Strategy**: Exponential backoff with 3 attempts (1s, 2s, 4s) for transient failures
+
+**Failed Email Storage**: After retry exhaustion, store in MongoDB failedEmails collection
+
+### Error Categories and Responses
+
+**User Errors (4xx)**:
+- Invalid Email Format: 400 "OAuth 2.0 User Email must be valid email format"
+- Missing Credentials: 400 "OAuth 2.0 Client ID, Client Secret, and Refresh Token are required"
+- Unauthorized: 401 "Admin authentication required"
+
+**System Errors (5xx)**:
+- Token Refresh Failure: Log with Google API error code
+- Network Timeout: Retry with exponential backoff
+- Account Suspension: Log critical error with full context
+- Encryption Failure: 500 "Failed to encrypt OAuth 2.0 credentials"
+
+**Business Logic Errors (422)**:
+- Incomplete Configuration: isMailerSetup = false, display alert banner
+- Invalid Refresh Token: Log error code invalid_grant
+
+### Error Handling Implementation
+
+```typescript
+async sendWithRetry(config: EmailConfig, maxRetries = 3): Promise<SendResult> {
+  const backoffIntervals = [1000, 2000, 4000];
+
+  for (let attempt = 1; attempt <= maxRetries; attempt++) {
+    try {
+      const result = await this.mailer.sendMail(config);
+      logger.info('OAuth 2.0 email sent successfully', {
+        messageId: result.messageId,
+        recipient: config.to,
+        attempt,
+      });
+      return result;
+    } catch (error) {
+      logger.error(`OAuth 2.0 email send failed (attempt ${attempt}/${maxRetries})`, {
+        error: error.message,
+        code: error.code,
+        user: config.from,
+        recipient: config.to,
+        attemptNumber: attempt,
+        timestamp: new Date().toISOString(),
+      });
+
+      if (attempt === maxRetries) {
+        await this.storeFailedEmail(config, error);
+        throw new Error(`OAuth 2.0 email send failed after ${maxRetries} attempts`);
+      }
+
+      await this.exponentialBackoff(attempt);
+    }
+  }
+}
+
+async exponentialBackoff(attempt: number): Promise<void> {
+  const backoffIntervals = [1000, 2000, 4000];
+  const delay = backoffIntervals[attempt - 1] || 4000;
+  return new Promise(resolve => setTimeout(resolve, delay));
+}
+
+async storeFailedEmail(config: EmailConfig, error: Error): Promise<void> {
+  const failedEmail = {
+    emailConfig: config,
+    error: {
+      message: error.message,
+      code: (error as any).code,
+      stack: error.stack,
+    },
+    transmissionMethod: 'oauth2',
+    attempts: 3,
+    lastAttemptAt: new Date(),
+    createdAt: new Date(),
+  };
+
+  await this.crowi.model('FailedEmail').create(failedEmail);
+}
+```
+
+### Monitoring
+
+- All OAuth 2.0 errors logged with context
+- Error codes tagged: oauth2_token_refresh_failure, oauth2_invalid_credentials, gmail_api_error
+- isMailerSetup flag exposed in admin UI
+- Never log clientSecret or refreshToken in plain text
+
+## Testing Strategy
+
+### Unit Tests
+
+- createOAuth2Client() with valid/missing/invalid credentials
+- initialize() sets isMailerSetup correctly
+- sendWithRetry() succeeds, retries, logs errors
+- exponentialBackoff() waits correct intervals
+- storeFailedEmail() creates document
+- ConfigManager encryption/decryption
+- AdminAppContainer state methods
+
+### Integration Tests
+
+- End-to-end email send with mocked transport
+- Token refresh triggered
+- Retry logic on failures
+- Failed email storage
+- OAuth2Setting component rendering
+- Field masking display
+- API validation
+
+### E2E Tests
+
+**Happy Path**:
+1. Navigate to Mail Settings
+2. Select OAuth 2.0
+3. Enter credentials
+4. Click Update
+5. Verify success toast
+6. Send test email
+7. Verify success
+
+**Credential Masking**:
+1. Navigate to Mail Settings
+2. Select OAuth 2.0
+3. Verify masked values (****abcd)
+4. Focus field, verify mask clears
+
+**Method Switching**:
+1. Configure OAuth 2.0
+2. Switch to SMTP
+3. Switch back to OAuth 2.0
+4. Verify credentials preserved
+
+**Invalid Credentials**:
+1. Send test email with invalid token
+2. Verify error message
+3. Check logs for error code
+
+**Network Timeout**:
+1. Send email, mock timeout
+2. Verify 3 retry attempts
+3. Verify correct backoff intervals
+
+**Incomplete Config**:
+1. Enter partial OAuth 2.0 config
+2. Verify validation error
+3. Verify alert banner
+
+### Performance Tests
+
+- 100 emails with OAuth 2.0: verify 1-2 token refreshes
+- Token refresh latency < 2s
+- Config load < 100ms
+
+## Security Considerations
+
+### Threat Modeling
+
+- Credential Exposure: Encrypted at rest
+- Log Leakage: Filters prevent plain text output
+- Unauthorized Access: Admin authentication required
+- MITM: SSL/TLS validation enforced
+- Token Replay: Short-lived access tokens
+
+### Data Protection
+
+- Client Secret: Encrypted, never logged, masked (last 4), never returned
+- Refresh Token: Encrypted, never logged, masked (last 4), never returned
+- Access Token: Cached in memory, expires in 1 hour
+- User Email: Plain text, used for logging
+
+### Compliance
+
+- A02:2021 Cryptographic Failures: AES-256 encryption
+- A03:2021 Injection: Email validation
+- A07:2021 Auth Failures: Admin authentication required
+- A09:2021 Logging Failures: Context logged, no credentials
+
+## Migration Strategy
+
+### Backward Compatibility
+
+- Zero breaking changes
+- Existing SMTP/SES unmodified
+- OAuth 2.0 added as new option
+
+### Rollback Plan
+
+- Revert code removes OAuth 2.0 option
+- SMTP/SES configs unaffected
+- OAuth 2.0 configs remain in database
+
+### Deployment Checklist
+
+**Pre-Deployment**:
+- Run test suite
+- Verify nodemailer v6.x+
+- Confirm encryption key
+- Review Gmail API quotas
+
+**Post-Deployment**:
+- Verify OAuth 2.0 option appears
+- Test OAuth 2.0 credentials
+- Monitor logs 24 hours
+- Update documentation
+
+**Production Validation**:
+- Send test email
+- Confirm token refresh after 1 hour
+- Verify encryption in MongoDB
+- Test method switching

+ 100 - 0
.kiro/specs/oauth2-email-support/requirements.md

@@ -0,0 +1,100 @@
+# Requirements Document
+
+## Project Description (Input)
+OAuth 2.0 authentication で Google Workspace を利用し email を送信する機能を追加したい
+
+### Context from User
+This implementation adds OAuth 2.0 authentication support for sending emails using Google Workspace accounts. The feature is fully integrated into the admin settings UI and follows the existing patterns for SMTP and SES configuration.
+
+Key configuration parameters:
+- Email Address: The authorized Google account email
+- Client ID: OAuth 2.0 Client ID from Google Cloud Console
+- Client Secret: OAuth 2.0 Client Secret
+- Refresh Token: OAuth 2.0 Refresh Token obtained from authorization flow
+
+The implementation uses nodemailer's built-in Gmail OAuth 2.0 support, which handles token refresh automatically.
+
+## Introduction
+
+This specification defines the requirements for adding OAuth 2.0 authentication support for email transmission using Google Workspace accounts in GROWI. The feature enables administrators to configure email sending through Google's Gmail API using OAuth 2.0 credentials instead of traditional SMTP authentication. This provides enhanced security through token-based authentication and follows Google's recommended practices for application email integration.
+
+## Requirements
+
+### Requirement 1: OAuth 2.0 Configuration Management
+
+**Objective:** As a GROWI administrator, I want to configure OAuth 2.0 credentials for Google Workspace email sending, so that the system can securely send emails without using SMTP passwords.
+
+#### Acceptance Criteria
+
+1. The Admin Settings UI shall provide a new transmission method option "OAuth 2.0 (Google Workspace)" alongside existing SMTP and SES options
+2. When OAuth 2.0 transmission method is selected, the Mail Settings interface shall display configuration fields for Email Address, Client ID, Client Secret, and Refresh Token
+3. The Mail Settings Service shall validate that Email Address is a valid email format before saving configuration
+4. The Mail Settings Service shall validate that Client ID, Client Secret, and Refresh Token are non-empty strings before saving configuration
+5. The Mail Settings Service shall securely store OAuth 2.0 credentials in the database with encryption for Client Secret and Refresh Token
+6. When configuration is saved successfully, the Mail Settings Service shall confirm save operation to the administrator
+7. If configuration save fails, then the Mail Settings Service shall display a descriptive error message indicating which field caused the failure
+
+### Requirement 2: Email Sending Functionality
+
+**Objective:** As a GROWI system, I want to send emails using OAuth 2.0 authenticated Google Workspace accounts, so that notifications and system emails can be delivered securely without SMTP credentials.
+
+#### Acceptance Criteria
+
+1. When OAuth 2.0 is configured as the transmission method, the Email Service shall use nodemailer with Gmail OAuth 2.0 transport for sending emails
+2. When sending an email, the Email Service shall authenticate to Gmail API using the configured Client ID, Client Secret, and Refresh Token
+3. The Email Service shall set the FROM address to the configured Email Address for all outgoing emails
+4. When email is sent successfully, the Email Service shall log the successful transmission with timestamp and recipient information
+5. The Email Service shall support sending emails with plain text body, HTML body, attachments, and standard email headers (subject, to, cc, bcc)
+6. When multiple emails are queued, the Email Service shall process them sequentially while maintaining OAuth 2.0 session state
+
+### Requirement 3: Token Management
+
+**Objective:** As a GROWI system, I want to automatically manage OAuth 2.0 access token lifecycle, so that email sending continues without manual intervention when tokens expire.
+
+#### Acceptance Criteria
+
+1. The Email Service shall use nodemailer's automatic token refresh mechanism to obtain new access tokens when needed
+2. When the refresh token is used, the Email Service shall request a new access token from Google's OAuth 2.0 token endpoint
+3. If token refresh succeeds, then the Email Service shall continue with email sending operation using the new access token
+4. If token refresh fails due to invalid refresh token, then the Email Service shall log an error and notify administrators of authentication failure
+5. The Email Service shall cache access tokens in memory and reuse them until expiration to minimize token refresh requests
+6. When OAuth 2.0 configuration is updated, the Email Service shall invalidate cached tokens and re-authenticate on next send operation
+
+### Requirement 4: Admin UI Integration
+
+**Objective:** As a GROWI administrator, I want OAuth 2.0 email configuration to follow the same UI patterns as SMTP and SES, so that I can configure it consistently with existing mail settings.
+
+#### Acceptance Criteria
+
+1. The Mail Settings page shall display OAuth 2.0 configuration form with the same visual styling and layout patterns as SMTP and SES sections
+2. When transmission method is changed from OAuth 2.0 to another method, the Mail Settings UI shall preserve entered OAuth 2.0 credentials without deleting them
+3. The Mail Settings UI shall provide field-level help text explaining each OAuth 2.0 parameter and how to obtain it from Google Cloud Console
+4. When displaying saved OAuth 2.0 configuration, the Mail Settings UI shall mask the Client Secret and Refresh Token fields showing only the last 4 characters
+5. The Mail Settings page shall provide a "Test Email" button that sends a test email using the configured OAuth 2.0 settings
+6. When test email is sent, the Mail Settings Service shall display success or failure status with detailed error information if sending fails
+
+### Requirement 5: Error Handling and Security
+
+**Objective:** As a GROWI administrator, I want clear error messages and secure credential handling, so that I can troubleshoot configuration issues and ensure credentials are protected.
+
+#### Acceptance Criteria
+
+1. If authentication fails due to invalid credentials, then the Email Service shall log the specific OAuth 2.0 error code and message from Google's API
+2. If email sending fails due to network timeout, then the Email Service shall retry the operation up to 3 times with exponential backoff
+3. If email sending fails after all retries, then the Email Service shall log the final failure and store the failed email for manual review
+4. The Mail Settings Service shall never log or display Client Secret or Refresh Token values in plain text in logs or error messages
+5. The Mail Settings Service shall require admin authentication before displaying OAuth 2.0 configuration page
+6. If OAuth 2.0 credentials are deleted from configuration, then the Email Service shall immediately stop attempting to send emails via OAuth 2.0 and fall back to default transmission method or display configuration error
+7. The Email Service shall validate SSL/TLS certificates when connecting to Google's OAuth 2.0 and Gmail API endpoints
+
+### Requirement 6: Migration and Compatibility
+
+**Objective:** As a GROWI system, I want OAuth 2.0 email support to coexist with existing SMTP and SES configurations, so that administrators can choose the most appropriate transmission method for their deployment.
+
+#### Acceptance Criteria
+
+1. The Mail Settings Service shall maintain backward compatibility with existing SMTP and SES configurations without requiring migration
+2. When transmission method is set to OAuth 2.0, the Email Service shall not use SMTP or SES credentials even if they are configured
+3. The Mail Settings Service shall allow switching between transmission methods (SMTP, SES, OAuth 2.0) without data loss
+4. If no transmission method is configured, then the Email Service shall display a configuration error when attempting to send emails
+5. The Mail Settings API shall expose OAuth 2.0 configuration status through existing admin API endpoints following the same schema pattern as SMTP/SES

+ 241 - 0
.kiro/specs/oauth2-email-support/research.md

@@ -0,0 +1,241 @@
+# Research & Design Decisions
+
+---
+**Purpose**: Capture discovery findings, architectural investigations, and rationale that inform the technical design for OAuth 2.0 email support.
+
+**Usage**:
+- Log research activities and outcomes during the discovery phase.
+- Document design decision trade-offs that are too detailed for `design.md`.
+- Provide references and evidence for future audits or reuse.
+---
+
+## Summary
+- **Feature**: `oauth2-email-support`
+- **Discovery Scope**: Extension (integrating OAuth2 into existing mail service architecture)
+- **Key Findings**:
+  - Existing mail service supports SMTP and SES via transmission method pattern
+  - Nodemailer has built-in OAuth2 support for Gmail with automatic token refresh
+  - Admin UI follows modular pattern with separate setting components per transmission method
+  - Config management uses `mail:*` namespace with type-safe definitions
+
+## Research Log
+
+### Existing Mail Service Architecture
+
+- **Context**: Need to understand integration points for OAuth2 support
+- **Sources Consulted**:
+  - `apps/app/src/server/service/mail.ts` (MailService implementation)
+  - `apps/app/src/client/components/Admin/App/MailSetting.tsx` (Admin UI)
+  - `apps/app/src/server/service/config-manager/config-definition.ts` (Config schema)
+- **Findings**:
+  - MailService uses factory pattern: `createSMTPClient()`, `createSESClient()`
+  - Transmission method determined by `mail:transmissionMethod` config value ('smtp' | 'ses')
+  - `initialize()` method called on service startup and S2S message updates
+  - Nodemailer transporter created based on transmission method
+  - Admin UI uses conditional rendering for SMTP vs SES settings
+  - State management via AdminAppContainer (unstated pattern)
+  - Test email functionality exists for SMTP only
+- **Implications**:
+  - OAuth2 follows same pattern: add `createOAuth2Client()` method
+  - Extend `mail:transmissionMethod` type to `'smtp' | 'ses' | 'oauth2'`
+  - Create new `OAuth2Setting.tsx` component following SMTP/SES pattern
+  - Add OAuth2-specific config keys following `mail:*` namespace
+
+### Nodemailer OAuth2 Integration
+
+- **Context**: Verify OAuth2 support in nodemailer and configuration requirements
+- **Sources Consulted**:
+  - [OAuth2 | Nodemailer](https://nodemailer.com/smtp/oauth2)
+  - [Using Gmail | Nodemailer](https://nodemailer.com/usage/using-gmail)
+  - [Sending Emails Securely Using Node.js, Nodemailer, SMTP, Gmail, and OAuth2](https://dev.to/chandrapantachhetri/sending-emails-securely-using-node-js-nodemailer-smtp-gmail-and-oauth2-g3a)
+  - Web search: "nodemailer gmail oauth2 configuration 2026"
+- **Findings**:
+  - Nodemailer has first-class OAuth2 support with type `'OAuth2'`
+  - Configuration structure:
+    ```javascript
+    {
+      service: "gmail",
+      auth: {
+        type: "OAuth2",
+        user: "user@gmail.com",
+        clientId: process.env.GOOGLE_CLIENT_ID,
+        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
+        refreshToken: process.env.GOOGLE_REFRESH_TOKEN
+      }
+    }
+    ```
+  - Automatic access token refresh handled by nodemailer
+  - Requires `https://mail.google.com/` OAuth scope
+  - Gmail service shortcut available (simplifies configuration)
+  - Production consideration: Gmail designed for individual users, not automated services
+- **Implications**:
+  - No additional dependencies needed (nodemailer already installed)
+  - Four config values required: user email, clientId, clientSecret, refreshToken
+  - Token refresh is automatic - no manual refresh logic needed
+  - Should validate credentials before saving to config
+  - Security: clientSecret and refreshToken must be encrypted in database
+
+### Config Manager Pattern Analysis
+
+- **Context**: Understand how to add new config keys for OAuth2 credentials
+- **Sources Consulted**:
+  - `apps/app/src/server/service/config-manager/config-definition.ts`
+  - Existing mail config keys: `mail:from`, `mail:transmissionMethod`, `mail:smtpHost`, etc.
+- **Findings**:
+  - Config keys use namespace pattern: `mail:*`
+  - Type-safe definitions using `defineConfig<T>()`
+  - Existing transmission method: `defineConfig<'smtp' | 'ses' | undefined>()`
+  - Config values stored in database via ConfigManager
+  - No explicit encryption layer visible in config definition (handled elsewhere)
+- **Implications**:
+  - Add four new keys: `mail:oauth2User`, `mail:oauth2ClientId`, `mail:oauth2ClientSecret`, `mail:oauth2RefreshToken`
+  - Update `mail:transmissionMethod` type to `'smtp' | 'ses' | 'oauth2' | undefined`
+  - Encryption should be handled at persistence layer (ConfigManager or database model)
+  - Follow same pattern as SMTP/SES for consistency
+
+### Admin UI State Management Pattern
+
+- **Context**: Understand how to integrate OAuth2 settings into admin UI
+- **Sources Consulted**:
+  - `apps/app/src/client/components/Admin/App/SmtpSetting.tsx`
+  - `apps/app/src/client/components/Admin/App/SesSetting.tsx`
+  - `apps/app/src/client/services/AdminAppContainer.js`
+- **Findings**:
+  - Separate component per transmission method (SmtpSetting, SesSetting)
+  - Components receive `register` from react-hook-form
+  - Unstated container pattern for state management
+  - Container methods: `changeSmtpHost()`, `changeFromAddress()`, etc.
+  - `updateMailSettingHandler()` saves all settings via API
+  - Test email button only shown for SMTP
+- **Implications**:
+  - Create `OAuth2Setting.tsx` component following same structure
+  - Add four state methods to AdminAppContainer: `changeOAuth2User()`, `changeOAuth2ClientId()`, etc.
+  - Include OAuth2 credentials in `updateMailSettingHandler()` API call
+  - Test email functionality should work for OAuth2 (same as SMTP)
+  - Field masking needed for clientSecret and refreshToken
+
+### Security Considerations
+
+- **Context**: Ensure secure handling of OAuth2 credentials
+- **Sources Consulted**:
+  - GROWI security guidelines (`.claude/rules/security.md`)
+  - Existing SMTP/SES credential handling
+- **Findings**:
+  - Credentials stored in MongoDB via ConfigManager
+  - Input fields use `type="password"` for sensitive values
+  - No explicit encryption visible in UI layer
+  - Logging should not expose credentials
+- **Implications**:
+  - Use `type="password"` for clientSecret and refreshToken fields
+  - Mask values when displaying saved configuration (show last 4 characters)
+  - Never log credentials in plain text
+  - Validate SSL/TLS when connecting to Google OAuth endpoints
+  - Ensure admin authentication required before accessing config page
+
+## Architecture Pattern Evaluation
+
+| Option | Description | Strengths | Risks / Limitations | Notes |
+|--------|-------------|-----------|---------------------|-------|
+| Factory Method Extension | Add `createOAuth2Client()` to existing MailService | Follows existing pattern, minimal changes, consistent with SMTP/SES | None significant | Recommended - aligns with current architecture |
+| Separate OAuth2Service | Create dedicated service for OAuth2 mail | Better separation of concerns | Over-engineering for simple extension, breaks existing pattern | Not recommended - unnecessary complexity |
+| Adapter Pattern | Wrap OAuth2 in adapter implementing mail interface | More flexible for future auth methods | Premature abstraction, more code to maintain | Not needed for single OAuth2 implementation |
+
+## Design Decisions
+
+### Decision: Extend Existing MailService with OAuth2 Support
+
+- **Context**: Need to add OAuth2 email sending without breaking existing SMTP/SES functionality
+- **Alternatives Considered**:
+  1. Create separate OAuth2MailService - more modular but introduces service management complexity
+  2. Refactor to plugin architecture - future-proof but over-engineered for current needs
+  3. Extend existing MailService with factory method - follows current pattern
+- **Selected Approach**: Extend existing MailService with `createOAuth2Client()` method
+- **Rationale**:
+  - Maintains consistency with existing architecture
+  - Minimal code changes reduce risk
+  - Clear migration path (no breaking changes)
+  - GROWI already uses this pattern successfully for SMTP/SES
+- **Trade-offs**:
+  - Benefits: Low risk, fast implementation, familiar pattern
+  - Compromises: All transmission methods in single service (acceptable given simplicity)
+- **Follow-up**: Ensure test coverage for OAuth2 path alongside existing SMTP/SES tests
+
+### Decision: Use Nodemailer's Built-in OAuth2 Support
+
+- **Context**: Need reliable OAuth2 implementation with automatic token refresh
+- **Alternatives Considered**:
+  1. Manual OAuth2 implementation with googleapis library - more control but complex
+  2. Third-party OAuth2 wrapper - additional dependency
+  3. Nodemailer built-in OAuth2 - zero additional dependencies
+- **Selected Approach**: Use nodemailer's native OAuth2 support with Gmail service
+- **Rationale**:
+  - No additional dependencies (nodemailer already installed)
+  - Automatic token refresh reduces complexity
+  - Well-documented and actively maintained
+  - Matches user's original plan (stated in requirements)
+- **Trade-offs**:
+  - Benefits: Simple, reliable, no new dependencies
+  - Compromises: Limited to Gmail/Google Workspace (acceptable per requirements)
+- **Follow-up**: Document Google Cloud Console setup steps for administrators
+
+### Decision: Preserve Existing Transmission Method Pattern
+
+- **Context**: Maintain backward compatibility while adding OAuth2 option
+- **Alternatives Considered**:
+  1. Deprecate transmission method concept - breaking change
+  2. Add OAuth2 as transmission method option - extends existing pattern
+  3. Support multiple simultaneous methods - unnecessary complexity
+- **Selected Approach**: Add 'oauth2' as third transmission method option
+- **Rationale**:
+  - Zero breaking changes for existing users
+  - Consistent admin UI experience
+  - Clear mutual exclusivity (one method active at a time)
+  - Easy to test and validate
+- **Trade-offs**:
+  - Benefits: Backward compatible, simple mental model
+  - Compromises: Only one transmission method active (acceptable per requirements)
+- **Follow-up**: Ensure switching between methods preserves all config values
+
+### Decision: Component-Based UI Following SMTP/SES Pattern
+
+- **Context**: Need consistent admin UI for OAuth2 configuration
+- **Alternatives Considered**:
+  1. Inline OAuth2 fields in main form - cluttered UI
+  2. Modal dialog for OAuth2 setup - breaks existing pattern
+  3. Separate OAuth2Setting component - matches SMTP/SES pattern
+- **Selected Approach**: Create `OAuth2Setting.tsx` component rendered conditionally
+- **Rationale**:
+  - Maintains visual consistency across transmission methods
+  - Reuses existing form patterns (react-hook-form, unstated)
+  - Easy for admins familiar with SMTP/SES setup
+  - Supports incremental development (component isolation)
+- **Trade-offs**:
+  - Benefits: Consistent UX, modular code, easy testing
+  - Compromises: Minor code duplication in form field rendering (acceptable)
+- **Follow-up**: Add help text for each OAuth2 field explaining Google Cloud Console setup
+
+## Risks & Mitigations
+
+- **Risk**: OAuth2 credentials stored in plain text in database
+  - **Mitigation**: Implement encryption at ConfigManager persistence layer; use same encryption as SMTP passwords
+
+- **Risk**: Refresh token expiration or revocation not handled
+  - **Mitigation**: Nodemailer handles refresh automatically; log specific error codes for troubleshooting; document token refresh in admin help text
+
+- **Risk**: Google rate limiting or account suspension
+  - **Mitigation**: Document production usage considerations; implement exponential backoff retry logic; log detailed error responses from Gmail API
+
+- **Risk**: Incomplete credential configuration causing service failure
+  - **Mitigation**: Validate all four required fields before saving; display clear error messages; maintain isMailerSetup flag for health checks
+
+- **Risk**: Breaking changes to existing SMTP/SES functionality
+  - **Mitigation**: Preserve all existing code paths; add OAuth2 as isolated branch; comprehensive integration tests for all three methods
+
+## References
+
+- [OAuth2 | Nodemailer](https://nodemailer.com/smtp/oauth2) - Official OAuth2 configuration documentation
+- [Using Gmail | Nodemailer](https://nodemailer.com/usage/using-gmail) - Gmail-specific integration guide
+- [Sending Emails Securely Using Node.js, Nodemailer, SMTP, Gmail, and OAuth2](https://dev.to/chandrapantachhetri/sending-emails-securely-using-node-js-nodemailer-smtp-gmail-and-oauth2-g3a) - Implementation tutorial
+- [Using OAuth2 with Nodemailer for Secure Email Sending](https://shazaali.substack.com/p/using-oauth2-with-nodemailer-for) - Security best practices
+- Internal: `apps/app/src/server/service/mail.ts` - Existing mail service implementation
+- Internal: `apps/app/src/client/components/Admin/App/MailSetting.tsx` - Admin UI patterns

+ 22 - 0
.kiro/specs/oauth2-email-support/spec.json

@@ -0,0 +1,22 @@
+{
+  "feature_name": "oauth2-email-support",
+  "created_at": "2026-02-06T11:43:56Z",
+  "updated_at": "2026-02-06T12:50:00Z",
+  "language": "en",
+  "phase": "tasks-approved",
+  "approvals": {
+    "requirements": {
+      "generated": true,
+      "approved": true
+    },
+    "design": {
+      "generated": true,
+      "approved": true
+    },
+    "tasks": {
+      "generated": true,
+      "approved": true
+    }
+  },
+  "ready_for_implementation": true
+}

+ 396 - 0
.kiro/specs/oauth2-email-support/tasks.md

@@ -0,0 +1,396 @@
+# Implementation Tasks - OAuth 2.0 Email Support
+
+## Status Overview
+
+**Current Phase**: Post-Implementation Improvement
+**Baseline**: GitHub Copilot completed basic OAuth 2.0 functionality (Config, Mail Service, API, UI, State Management, Translations)
+**Focus**: Address critical gaps for production readiness
+
+### Implementation Status
+
+✅ **Completed** (12 tasks): Basic OAuth 2.0 functionality working
+- Configuration schema
+- OAuth 2.0 transport creation (basic)
+- API endpoints and validation
+- Frontend components and state management
+- Multi-language translations
+
+⚠️ **Partially Complete** (2 tasks): Basic functionality exists but missing enhancements
+- Help text (2 of 4 fields complete)
+- Test email support (needs verification)
+
+❌ **Not Implemented** (15 tasks): Critical gaps identified in validation
+- Error handling with retry logic
+- Failed email storage
+- Field masking in UI
+- Complete help text
+- All test coverage
+
+---
+
+## Priority Tasks (Recommended Approach)
+
+### 🔴 Phase A: Critical Production Requirements (Immediate - 4-6 hours)
+
+These tasks are **mandatory before production deployment** to ensure reliability and proper error handling.
+
+- [ ] 1. Implement retry logic with exponential backoff
+  - Wrap email sending with automatic retry mechanism (3 attempts)
+  - Apply exponential backoff intervals: 1 second, 2 seconds, 4 seconds
+  - Log detailed error context on each failed attempt
+  - Extract and log Google API error codes (invalid_grant, insufficient_permission, unauthorized_client)
+  - Continue with existing email send flow on success
+  - _Requirements: 5.1, 5.2_
+  - _Components: MailService.sendWithRetry(), MailService.exponentialBackoff()_
+  - _Priority: P0 (Blocking)_
+
+- [ ] 2. Implement failed email storage
+  - Create database schema for failed email tracking
+  - Store email configuration after retry exhaustion
+  - Capture error details (message, code, stack), transmission method, attempt count
+  - Add createdAt and lastAttemptAt timestamps for tracking
+  - Enable manual review and reprocessing via admin interface
+  - _Requirements: 5.3_
+  - _Components: MailService.storeFailedEmail(), FailedEmail model_
+  - _Priority: P0 (Blocking)_
+
+- [ ] 3. Enhance OAuth 2.0 error logging
+  - Ensure credentials never logged in plain text (verify existing implementation)
+  - Log client ID with only last 4 characters visible
+  - Include user email, timestamp, and error context in all OAuth 2.0 error logs
+  - Verify SSL/TLS validation for Google OAuth endpoints
+  - Add monitoring tags for error categorization (oauth2_token_refresh_failure, gmail_api_error)
+  - _Requirements: 5.4, 5.7_
+  - _Components: MailService error handlers, logging infrastructure_
+  - _Priority: P0 (Blocking)_
+
+### 🟡 Phase B: Essential Test Coverage (Next - 8-12 hours)
+
+These tests are **essential for production confidence** and prevent regressions.
+
+- [ ] 4. Unit tests: Mail service OAuth 2.0 transport
+  - Test createOAuth2Client() with valid credentials returns functional transport
+  - Test createOAuth2Client() with missing credentials returns null and logs error
+  - Test createOAuth2Client() with invalid email format logs error
+  - Test initialize() sets isMailerSetup flag correctly for OAuth 2.0
+  - Test mailer setup state when OAuth 2.0 credentials incomplete
+  - _Requirements: 2.1, 2.2, 6.2, 6.4_
+  - _Priority: P1 (High)_
+
+- [ ] 5. Unit tests: Retry logic and error handling
+  - Test sendWithRetry() succeeds on first attempt without retries
+  - Test retry mechanism with exponential backoff (verify 1s, 2s, 4s intervals)
+  - Test storeFailedEmail() called after 3 failed attempts
+  - Test error logging includes OAuth 2.0 context (error code, client ID last 4, timestamp)
+  - Verify credentials never appear in log output
+  - _Requirements: 5.1, 5.2, 5.3, 5.4_
+  - _Priority: P1 (High)_
+
+- [ ] 6. Unit tests: Configuration encryption
+  - Test client secret encrypted when saved to database (isSecret: true)
+  - Test refresh token encrypted when saved to database (isSecret: true)
+  - Test client secret decrypted correctly when loaded from database
+  - Test refresh token decrypted correctly when loaded from database
+  - Verify transmission method includes 'oauth2' value
+  - _Requirements: 1.5, 6.1_
+  - _Priority: P1 (High)_
+
+- [ ] 7. Integration test: OAuth 2.0 email sending flow
+  - Test end-to-end email send with mocked OAuth 2.0 transport
+  - Test token refresh triggered by nodemailer (mock Google OAuth API)
+  - Test retry logic invoked on transient Gmail API failures
+  - Test failed email storage after all retries exhausted
+  - Verify error context logged at each step
+  - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 5.2, 5.3_
+  - _Priority: P1 (High)_
+
+- [ ] 8. Integration test: API validation and security
+  - Test PUT /api/v3/app-settings with valid OAuth 2.0 credentials returns 200
+  - Test PUT with invalid email returns 400 with field-specific error
+  - Test PUT with missing credentials returns 400 with validation errors
+  - Test GET response never includes client secret or refresh token values
+  - Test S2S messaging triggered after successful configuration update
+  - _Requirements: 1.3, 1.4, 1.5, 1.6, 1.7, 5.5, 6.5_
+  - _Priority: P1 (High)_
+
+- [ ] 9. E2E test: Configuration and basic email flow
+  - Navigate to Mail Settings page as admin
+  - Select OAuth 2.0 transmission method
+  - Enter all four OAuth 2.0 credentials
+  - Save configuration and verify success notification
+  - Send test email and verify success/failure with detailed error if applicable
+  - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.6, 4.1, 4.5, 4.6_
+  - _Priority: P1 (High)_
+
+### 🟢 Phase C: UI Polish & Enhancements (Then - 3-4 hours)
+
+These tasks improve **user experience** but don't block production deployment.
+
+- [ ] 10. Complete help text for all OAuth 2.0 fields
+  - Add help text for oauth2ClientId: "Obtain from Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID"
+  - Add help text for oauth2ClientSecret: "Found in the same OAuth 2.0 Client ID details page"
+  - Verify existing help text for oauth2User and oauth2RefreshToken
+  - Ensure help text visible below each input field
+  - _Requirements: 4.3_
+  - _Priority: P2 (Medium)_
+
+- [ ] 11. Implement credential field masking
+  - Display saved client secret with masking: ****abcd (last 4 characters)
+  - Display saved refresh token with masking: ****abcd (last 4 characters)
+  - Clear mask when field receives focus to allow editing
+  - Preserve mask when field loses focus without changes
+  - Apply masking using AdminAppContainer state values
+  - _Requirements: 4.4_
+  - _Priority: P2 (Medium)_
+
+- [ ] 12. Verify test email support for OAuth 2.0
+  - Confirm test email button enabled when OAuth 2.0 is configured
+  - Verify test email functionality works with OAuth 2.0 transmission method
+  - Display detailed error messages with OAuth 2.0 error codes on failure
+  - Test end-to-end: configure OAuth 2.0 → send test email → verify success
+  - _Requirements: 4.5, 4.6_
+  - _Priority: P2 (Medium)_
+
+---
+
+## Completed Tasks (Baseline Implementation)
+
+<details>
+<summary>✅ Click to expand completed tasks from baseline implementation</summary>
+
+### Configuration & Foundation
+- [x] 1. Configuration schema for OAuth 2.0 credentials
+- [x] 1.1 Add OAuth 2.0 configuration keys
+  - Defined four new configuration keys (user, clientId, clientSecret, refreshToken)
+  - Extended transmission method enum to include 'oauth2'
+  - Enabled encryption for sensitive credentials (isSecret: true)
+  - Verified TypeScript type safety
+  - _Requirements: 1.1, 1.5, 6.1_
+
+### Mail Service Extension
+- [x] 2. OAuth 2.0 email transmission capability (partial)
+- [x] 2.1 Create OAuth 2.0 transport for Gmail
+  - Built OAuth 2.0 transport using nodemailer Gmail service
+  - Loads credentials from configuration manager
+  - Validates presence of all required fields
+  - Sets mailer setup flag based on success
+  - **Note**: Basic implementation without retry logic
+  - _Requirements: 2.1, 2.2, 3.1, 3.2, 3.3, 3.5, 6.2_
+
+- [x] 2.5 Service initialization and token management (partial)
+  - Integrated OAuth 2.0 into mail service initialization
+  - Handles mailer setup state for OAuth 2.0
+  - Maintains backward compatibility with SMTP/SES
+  - **Note**: Token invalidation on config change exists via S2S
+  - _Requirements: 2.3, 2.5, 2.6, 3.6, 5.6, 6.2, 6.4_
+
+### API Layer
+- [x] 3. OAuth 2.0 configuration management endpoints
+- [x] 3.1 OAuth 2.0 settings validation and persistence
+  - Accepts OAuth 2.0 credentials in API request body
+  - Validates email address format
+  - Validates non-empty strings for all credentials
+  - Enforces field length limits
+  - _Requirements: 1.3, 1.4_
+
+- [x] 3.2 OAuth 2.0 settings persistence and S2S messaging
+  - Persists credentials via configuration manager
+  - Triggers S2S messaging for config updates
+  - Returns success response with mailer status
+  - Never returns sensitive credentials in GET responses
+  - _Requirements: 1.5, 1.6, 5.5, 6.5_
+
+- [x] 3.3 Field-specific validation error messages
+  - Generates descriptive error messages per field
+  - Returns 400 Bad Request with validation details
+  - _Requirements: 1.7_
+
+### Frontend Components
+- [x] 4. OAuth 2.0 admin UI components
+- [x] 4.1 OAuth 2.0 settings component
+  - Created OAuth2Setting component with four input fields
+  - Applied password type for sensitive fields
+  - Follows SMTP/SES visual patterns
+  - Integrated with react-hook-form
+  - _Requirements: 1.2, 4.1_
+
+### State Management
+- [x] 5. OAuth 2.0 state management integration
+- [x] 5.1 AdminAppContainer OAuth 2.0 state
+  - Added four state properties for OAuth 2.0 credentials
+  - Created state setter methods for each field
+  - Preserves credentials when switching transmission methods
+  - _Requirements: 4.2, 6.3_
+
+- [x] 5.2 Mail settings form submission
+  - Includes OAuth 2.0 credentials in API payload
+  - Validates email format before submission
+  - Displays success/error toast notifications
+  - _Requirements: 1.3, 1.6, 1.7_
+
+- [x] 5.3 Transmission method selection integration
+  - Added 'oauth2' to transmission method options
+  - Conditionally renders OAuth2Setting component
+  - Maintains UI consistency with SMTP/SES
+  - _Requirements: 1.1, 1.2_
+
+### Internationalization
+- [x] 6. Multi-language support for OAuth 2.0 UI
+- [x] 6.1 Translation keys for OAuth 2.0 settings
+  - Added translation keys for OAuth 2.0 label and description
+  - Added translation keys for all field labels
+  - Covered all supported languages (en, ja, fr, ko, zh)
+  - **Note**: Help text only exists for 2 of 4 fields
+  - _Requirements: 1.2, 4.1, 4.3_
+
+</details>
+
+---
+
+## Deferred Tasks (Optional Enhancements)
+
+<details>
+<summary>📋 Click to expand optional/deferred tasks</summary>
+
+These tasks provide additional test coverage and validation but are not blocking for initial production deployment.
+
+### Additional UI Component Tests
+- [ ]* 13. OAuth 2.0 UI component rendering tests
+  - Test OAuth2Setting component renders with all four input fields
+  - Test react-hook-form integration and field registration
+  - Test help text displays correctly
+  - Test component follows SMTP/SES styling patterns
+  - _Requirements: 1.2, 4.1, 4.3_
+  - _Priority: P3 (Optional)_
+
+### Additional State Management Tests
+- [ ]* 14. AdminAppContainer state management tests
+  - Test OAuth 2.0 state properties initialize correctly
+  - Test state setter methods update credentials
+  - Test OAuth 2.0 credentials included in API payload when method is 'oauth2'
+  - Test email validation rejects invalid format
+  - Test credentials preserved when switching methods
+  - _Requirements: 1.3, 4.2, 6.3_
+  - _Priority: P3 (Optional)_
+
+### E2E User Flow Tests
+- [ ]* 15. E2E: Credential masking and preservation
+  - Test masked credentials display (****abcd format)
+  - Test mask clears on field focus
+  - Test switching transmission methods preserves credentials
+  - _Requirements: 4.2, 4.4, 6.3_
+  - _Priority: P3 (Optional)_
+
+- [ ]* 16. E2E: Error handling scenarios
+  - Test invalid credentials display error message
+  - Test incomplete configuration shows validation errors
+  - Test mailer not setup displays alert banner
+  - _Requirements: 1.7, 5.1, 6.4_
+  - _Priority: P3 (Optional)_
+
+### Backward Compatibility Verification
+- [ ]* 17. SMTP and SES regression testing
+  - Verify SMTP email sending unchanged
+  - Verify SES email sending unchanged
+  - Test switching between SMTP, SES, OAuth 2.0 preserves all credentials
+  - Test only active transmission method used
+  - Test mixed deployment scenarios
+  - _Requirements: 6.1, 6.2, 6.3_
+  - _Priority: P3 (Optional)_
+
+</details>
+
+---
+
+## Requirements Coverage Summary
+
+**Total Requirements**: 37
+**Requirements with Priority Tasks**: 12 (critical for production)
+**Requirements Fully Covered by Baseline**: 25
+
+| Phase | Requirements | Coverage |
+|-------|--------------|----------|
+| Phase A (Critical) | 5.1, 5.2, 5.3, 5.4, 5.7 | Error handling and logging |
+| Phase B (Testing) | 2.1-2.6, 3.1-3.6, 5.1-5.5, 6.2, 6.4, 6.5 | Test coverage for all critical paths |
+| Phase C (UI Polish) | 4.3, 4.4, 4.5, 4.6 | User experience enhancements |
+| Baseline Complete | 1.1-1.7, 2.1, 2.3, 2.5, 3.6, 4.1, 4.2, 5.6, 6.1, 6.3 | Core functionality working |
+
+---
+
+## Execution Guidance
+
+### Quick Start (Recommended)
+
+Execute priority tasks in order:
+
+```bash
+# Phase A: Critical Production Requirements (4-6 hours)
+/kiro:spec-impl oauth2-email-support 1,2,3 -y
+
+# Phase B: Essential Test Coverage (8-12 hours)
+/kiro:spec-impl oauth2-email-support 4,5,6,7,8,9 -y
+
+# Phase C: UI Polish (3-4 hours)
+/kiro:spec-impl oauth2-email-support 10,11,12 -y
+```
+
+### Context Management
+
+⚠️ **IMPORTANT**: Clear conversation history between phases to avoid context bloat:
+- Clear after Phase A before starting Phase B
+- Clear after Phase B before starting Phase C
+- Each phase is self-contained
+
+### Verification After Each Phase
+
+**After Phase A**:
+```bash
+# Verify retry logic works
+npm test -- mail.spec
+
+# Check error logging
+grep -r "sendWithRetry\|storeFailedEmail" apps/app/src/server/service/mail.ts
+```
+
+**After Phase B**:
+```bash
+# Run full test suite
+cd apps/app && pnpm test
+
+# Verify coverage
+pnpm test -- --coverage
+```
+
+**After Phase C**:
+```bash
+# Manual UI verification
+# 1. Start dev server
+# 2. Navigate to Admin → App → Mail Settings
+# 3. Test OAuth 2.0 configuration with masking
+```
+
+---
+
+## Production Readiness Checklist
+
+Before deploying to production, ensure:
+
+- [ ] **Phase A Complete**: Retry logic, failed email storage, enhanced logging implemented
+- [ ] **Phase B Complete**: All essential tests passing (mail service, API, E2E config flow)
+- [ ] **Phase C Complete**: UI polish (help text, masking, test email) implemented
+- [ ] **Integration Tests Pass**: Run `pnpm test` in apps/app with no failures
+- [ ] **Manual Verification**: Admin can configure OAuth 2.0 and send test email successfully
+- [ ] **Error Handling Verified**: Test with invalid credentials to confirm proper error messages
+- [ ] **Backward Compatibility**: Verify existing SMTP/SES functionality unaffected
+
+---
+
+## Notes
+
+**Baseline Implementation Source**: GitHub Copilot (completed Phases 1-6 from original task plan)
+
+**Validation Report Reference**: See `.kiro/specs/oauth2-email-support/validation-report.md` for detailed gap analysis
+
+**Task Numbering**: Renumbered to reflect priority order (1-12 for priority tasks, 13-17 for optional)
+
+**Estimated Total Time**: 15-22 hours for priority tasks (Phases A-C)

+ 483 - 0
.kiro/specs/oauth2-email-support/validation-report.md

@@ -0,0 +1,483 @@
+# OAuth 2.0 Email Support - Validation Report
+
+**Date**: 2026-02-06
+**Spec**: oauth2-email-support
+**Phase**: Post-Implementation Review
+**Reviewer**: Claude Code (AI Agent)
+
+## Executive Summary
+
+This report analyzes the gap between the approved design document and the GitHub Copilot implementation of OAuth 2.0 email support. The implementation successfully delivers core functionality with **~80% design adherence**, but exhibits several notable gaps in error handling, UI polish, and testing coverage.
+
+**Overall Assessment**: ⚠️ **FUNCTIONAL BUT INCOMPLETE**
+
+The implementation is production-ready for basic OAuth 2.0 email sending, but requires additional work to meet the comprehensive quality standards specified in the design document, particularly in error handling, user experience refinements, and test coverage.
+
+---
+
+## 1. Design Quality Review
+
+### Review Summary
+
+The design document demonstrates strong architectural alignment with existing GROWI patterns, comprehensive requirements traceability, and thoughtful security considerations. The chosen approach (factory method extension) appropriately balances backward compatibility with new functionality. The design is well-structured and ready for implementation with only minor clarifications needed.
+
+### Critical Issues
+
+#### 🟡 Issue 1: Error Handling Specification Incomplete
+
+**Concern**: While the design specifies retry logic with exponential backoff (Requirement 5.2) and failed email storage (Requirement 5.3), the error handling flow diagrams and component specifications lack detailed implementation guidance for these mechanisms.
+
+**Impact**: Medium - Implementers may skip or oversimplify critical error handling, leading to poor production reliability and difficult troubleshooting when OAuth 2.0 failures occur.
+
+**Suggestion**: Add a dedicated "Error Handling Implementation" section with:
+- Concrete retry configuration (intervals: 1s, 2s, 4s)
+- Failed email storage schema and location
+- Error logging format examples with OAuth 2.0 context
+
+**Traceability**: Requirements 5.1, 5.2, 5.3 (Error Handling and Security)
+
+**Evidence**: Design section "Error Handling" (lines 786-860) provides error categories but lacks implementation-level detail for retry and storage mechanisms.
+
+#### 🟡 Issue 2: Test Strategy Missing E2E Test Scenarios
+
+**Concern**: The testing strategy (lines 860-922) specifies unit and integration tests comprehensively, but E2E test scenarios lack concrete user flows and expected outcomes for critical paths like token refresh failure recovery.
+
+**Impact**: Low-Medium - E2E test implementation may miss critical user-facing error scenarios, reducing confidence in production deployment.
+
+**Suggestion**: Enhance E2E test scenarios with:
+- Step-by-step user actions and expected UI states
+- Mock Google API responses for each scenario
+- Screenshot/video capture points for visual regression testing
+
+**Traceability**: All requirements (comprehensive validation)
+
+**Evidence**: Design section "E2E Tests" (lines 903-912) lists scenarios but lacks detailed test steps.
+
+#### 🟢 Issue 3: Field Masking Specification Ambiguous
+
+**Concern**: Requirement 4.4 specifies "mask sensitive fields showing only last 4 characters," but the OAuth2Setting component specification (lines 434-489) doesn't detail the masking implementation approach (client-side vs. server-side, edit behavior, etc.).
+
+**Impact**: Low - Minor UX inconsistency, but doesn't affect core functionality.
+
+**Suggestion**: Clarify masking behavior:
+- Display format: `****abcd` when field is populated but not edited
+- Allow full edit when user focuses field
+- Specify whether masking occurs on load or only after save
+
+**Traceability**: Requirement 4.4 (Admin UI Integration)
+
+**Evidence**: Design section "OAuth2Setting Component" (lines 434-489) and "Implementation Notes" (lines 483-489).
+
+### Design Strengths
+
+1. **Excellent Architecture Integration**: The factory method extension pattern seamlessly integrates OAuth 2.0 without disrupting existing SMTP/SES functionality. Clear separation of concerns with isolated `createOAuth2Client()` method follows established patterns perfectly.
+
+2. **Comprehensive Security Considerations**: Thorough threat modeling, encryption strategy, and credential handling guidelines (section "Security Considerations," lines 923-966) demonstrate mature security-first thinking. The OWASP Top 10 mitigations are well-addressed.
+
+### Final Assessment
+
+**Decision**: ✅ **GO (with recommendations)**
+
+**Rationale**: The design demonstrates solid architectural thinking and comprehensive requirements coverage. The identified issues are primarily documentation gaps rather than fundamental design flaws. The design provides sufficient guidance for implementation to proceed, with the understanding that error handling and testing details will be refined during implementation.
+
+**Next Steps**:
+1. Address error handling implementation details (Issue 1) during implementation
+2. Expand E2E test scenarios collaboratively with QA team
+3. Proceed to `/kiro:spec-tasks oauth2-email-support` to generate implementation tasks
+
+---
+
+## 2. Implementation Gap Analysis
+
+### Overview
+
+GitHub Copilot's implementation successfully delivers **core OAuth 2.0 functionality** (configuration, email sending, credential storage) but exhibits significant gaps in **error handling, UI polish, and testing**. The implementation is suitable for initial deployment but requires refinement to meet production quality standards.
+
+### Gap Summary Table
+
+| Category | Design Specification | Implementation Status | Gap Severity |
+|----------|---------------------|----------------------|--------------|
+| **Configuration** | 4 config keys with encryption | ✅ Fully implemented | None |
+| **Mail Service** | OAuth 2.0 transport creation | ✅ Fully implemented | None |
+| **API Routes** | OAuth 2.0 CRUD endpoints | ✅ Fully implemented | None |
+| **UI Components** | OAuth2Setting component | ⚠️ Partially implemented | Medium |
+| **Error Handling** | Retry + detailed logging | ❌ Not implemented | High |
+| **Field Masking** | Show last 4 chars of secrets | ❌ Not implemented | Medium |
+| **Help Text** | All 4 fields documented | ⚠️ Only 2 fields | Low |
+| **Testing** | Unit + Integration + E2E | ❌ Not implemented | High |
+| **S2S Messaging** | Config update broadcasts | ✅ Fully implemented | None |
+
+### Detailed Gap Analysis
+
+#### Gap 1: Error Handling Missing ❌ HIGH SEVERITY
+
+**Design Specification** (Requirements 5.1, 5.2, 5.3):
+- Log specific OAuth 2.0 error codes from Google API
+- Retry failed sends with exponential backoff (3 attempts: 1s, 2s, 4s)
+- Store failed emails for manual review after all retries
+
+**Implementation Reality**:
+```typescript
+// mail.ts - Current implementation (lines 188-224)
+createOAuth2Client(option?) {
+  // ... creates transport ...
+  const client = nodemailer.createTransport(option);
+  logger.debug('mailer set up for OAuth2', client);
+  return client;
+}
+// ❌ No retry logic
+// ❌ No detailed error logging with OAuth 2.0 context
+// ❌ No failed email storage mechanism
+```
+
+**Impact**:
+- Production issues will be difficult to troubleshoot without detailed error context
+- Transient failures (network timeouts) will result in lost emails instead of retries
+- Administrators have no visibility into failed email attempts
+
+**Recommendation**:
+Wrap the `send()` method in mail.ts with retry logic:
+```typescript
+async sendWithRetry(config, maxRetries = 3) {
+  for (let attempt = 1; attempt <= maxRetries; attempt++) {
+    try {
+      return await this.mailer.sendMail(config);
+    } catch (error) {
+      logger.error(`OAuth 2.0 email send failed (attempt ${attempt}/${maxRetries})`, {
+        error: error.message,
+        code: error.code,
+        user: config.from,
+      });
+      if (attempt === maxRetries) {
+        await this.storeFailedEmail(config, error);
+        throw error;
+      }
+      await this.exponentialBackoff(attempt);
+    }
+  }
+}
+```
+
+**Traceability**: Requirements 5.1, 5.2, 5.3
+
+---
+
+#### Gap 2: Field Masking Not Implemented ❌ MEDIUM SEVERITY
+
+**Design Specification** (Requirement 4.4):
+> "When displaying saved OAuth 2.0 configuration, the Mail Settings UI shall mask the Client Secret and Refresh Token fields showing only the last 4 characters"
+
+**Implementation Reality**:
+```tsx
+// OAuth2Setting.tsx - Current implementation
+<input
+  className="form-control"
+  type="password"           // ❌ Just hides everything with dots
+  id="admin-oauth2-client-secret"
+  {...register('oauth2ClientSecret')}
+/>
+```
+
+**Impact**:
+- Administrators cannot verify which credentials are configured without re-entering them
+- UX inconsistency compared to other password management patterns in admin UIs
+- Minor security risk: unable to confirm credential identity without exposing full value
+
+**Recommendation**:
+Add masking logic in OAuth2Setting component:
+```tsx
+const savedSecret = adminAppContainer.state.oauth2ClientSecret;
+const displayValue = savedSecret
+  ? `****${savedSecret.slice(-4)}`
+  : '';
+
+<input
+  className="form-control"
+  type="text"  // Change to text for masking display
+  placeholder={displayValue || "Enter client secret"}
+  {...register('oauth2ClientSecret')}
+/>
+```
+
+**Traceability**: Requirement 4.4
+
+---
+
+#### Gap 3: Incomplete Help Text ⚠️ LOW SEVERITY
+
+**Design Specification** (Requirement 4.3):
+> "The Mail Settings UI shall provide field-level help text explaining each OAuth 2.0 parameter and how to obtain it from Google Cloud Console"
+
+**Implementation Reality**:
+```tsx
+// OAuth2Setting.tsx
+// ✅ oauth2User: Has help text
+// ❌ oauth2ClientId: No help text
+// ❌ oauth2ClientSecret: No help text
+// ✅ oauth2RefreshToken: Has help text
+```
+
+**Impact**:
+- Administrators may struggle to configure OAuth 2.0 without clear guidance
+- Support burden increases due to configuration questions
+
+**Recommendation**:
+Add help text to all fields in translation files:
+```json
+{
+  "oauth2_client_id_help": "Obtain from Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID",
+  "oauth2_client_secret_help": "Found in the same OAuth 2.0 Client ID details page"
+}
+```
+
+**Traceability**: Requirement 4.3
+
+---
+
+#### Gap 4: No Testing Implementation ❌ HIGH SEVERITY
+
+**Design Specification** (Section "Testing Strategy"):
+- Unit tests for MailService, ConfigManager, AdminAppContainer
+- Integration tests for OAuth 2.0 email sending flow
+- E2E tests for configuration and email sending scenarios
+- Performance tests for token caching
+
+**Implementation Reality**:
+```bash
+$ find apps/app -name "*.spec.*" | xargs grep -l "oauth2"
+# No results - ZERO test coverage
+```
+
+**Impact**:
+- No automated validation of OAuth 2.0 functionality
+- Regression risk during future refactoring
+- Cannot verify error handling, token refresh, or credential security
+
+**Recommendation**:
+Prioritize test implementation in this order:
+1. **Unit tests** (mail.ts, config-definition.ts) - 2-3 hours
+2. **API integration tests** (app-settings endpoints) - 2-3 hours
+3. **Component tests** (OAuth2Setting.tsx) - 1-2 hours
+4. **E2E test** (happy path: configure + send) - 2-3 hours
+
+**Traceability**: All requirements (testing validates complete implementation)
+
+---
+
+#### Gap 5: Test Email Support Unclear ⚠️ MEDIUM SEVERITY
+
+**Design Specification** (Requirement 4.5):
+> "The Mail Settings page shall provide a 'Test Email' button that sends a test email using the configured OAuth 2.0 settings"
+
+**Implementation Reality**:
+The test email endpoint exists (`/app-settings/smtp-setting-smtp-test`), but:
+- API route is named "smtp-test" suggesting SMTP-only support
+- No evidence of OAuth 2.0 transmission method check in test email handler
+- Unclear if test button is enabled when transmission method is 'oauth2'
+
+**Impact**:
+- Administrators cannot validate OAuth 2.0 configuration before use
+- Higher risk of production email failures due to misconfiguration
+
+**Recommendation**:
+Verify and document test email support for OAuth 2.0:
+1. Check if `sendTestEmail()` function (line 708) handles 'oauth2' transmission method
+2. Ensure test button in MailSetting.tsx is enabled for OAuth 2.0
+3. Add explicit test case: "Send test email via OAuth 2.0"
+
+**Traceability**: Requirement 4.5
+
+---
+
+### Implementation Strengths
+
+1. **Clean Code Structure**: The implementation follows GROWI's coding standards excellently (named exports, TypeScript typing, feature-based organization).
+
+2. **Security Best Practices**: Credentials properly marked as `isSecret: true` in config definition, password fields used in UI, no plain text logging.
+
+3. **Backward Compatibility**: Implementation preserves SMTP/SES functionality completely - zero regression risk.
+
+4. **Internationalization**: Translations provided for all 5 supported languages (English, Japanese, French, Korean, Chinese).
+
+---
+
+## 3. Requirements Coverage Analysis
+
+### Coverage Summary
+
+| Requirement Category | Total Requirements | Fully Met | Partially Met | Not Met | Coverage % |
+|---------------------|-------------------|-----------|---------------|---------|------------|
+| 1. Configuration Management | 7 | 6 | 0 | 1 | 86% |
+| 2. Email Sending | 6 | 5 | 0 | 1 | 83% |
+| 3. Token Management | 6 | 6 | 0 | 0 | 100% |
+| 4. Admin UI Integration | 6 | 3 | 2 | 1 | 67% |
+| 5. Error Handling & Security | 7 | 4 | 0 | 3 | 57% |
+| 6. Migration & Compatibility | 5 | 5 | 0 | 0 | 100% |
+| **TOTAL** | **37** | **29** | **2** | **6** | **82%** |
+
+### Detailed Requirements Status
+
+#### ✅ Fully Implemented Requirements (29)
+
+**Configuration Management (6/7)**:
+- ✅ 1.1: OAuth 2.0 transmission method option added
+- ✅ 1.2: Configuration fields displayed when OAuth 2.0 selected
+- ✅ 1.3: Email format validation implemented
+- ✅ 1.4: Non-empty credential validation implemented
+- ✅ 1.5: Secure storage with encryption (isSecret: true)
+- ✅ 1.6: Success confirmation via toast notifications
+
+**Email Sending (5/6)**:
+- ✅ 2.1: Nodemailer Gmail OAuth 2.0 transport created
+- ✅ 2.2: Authentication to Gmail API with OAuth 2.0
+- ✅ 2.3: FROM address set to configured email
+- ✅ 2.5: All email content types supported (via nodemailer)
+- ✅ 2.6: Sequential email processing (existing behavior)
+
+**Token Management (6/6)**:
+- ✅ 3.1: Nodemailer automatic token refresh used
+- ✅ 3.2: Access token requested with refresh token
+- ✅ 3.3: Email sending continues after token refresh
+- ✅ 3.4: Error logging on refresh failure (basic)
+- ✅ 3.5: Access tokens cached in memory (nodemailer)
+- ✅ 3.6: Tokens invalidated on config update (via reinitialize)
+
+**Admin UI Integration (3/6)**:
+- ✅ 4.1: OAuth 2.0 form with consistent styling
+- ✅ 4.2: OAuth 2.0 credentials preserved when switching methods
+
+**Error Handling & Security (4/7)**:
+- ✅ 5.4: Credentials never logged in plain text
+- ✅ 5.5: Admin authentication required (existing middleware)
+- ✅ 5.6: OAuth 2.0 sending stops when credentials deleted
+- ✅ 5.7: SSL/TLS validation (nodemailer default)
+
+**Migration & Compatibility (5/5)**:
+- ✅ 6.1: Backward compatibility with SMTP/SES maintained
+- ✅ 6.2: Only active transmission method used
+- ✅ 6.3: Transmission method switching without data loss
+- ✅ 6.4: Configuration error display (via isMailerSetup flag)
+- ✅ 6.5: OAuth 2.0 status exposed via admin API
+
+#### ⚠️ Partially Implemented Requirements (2)
+
+**Admin UI Integration (2/6)**:
+- ⚠️ 4.3: Field-level help text (only 2 of 4 fields have help text)
+- ⚠️ 4.5: Test email button (existence unclear for OAuth 2.0)
+
+#### ❌ Not Implemented Requirements (6)
+
+**Configuration Management (1/7)**:
+- ❌ 1.7: Descriptive error messages (basic errors only, not field-specific)
+
+**Email Sending (1/6)**:
+- ❌ 2.4: Successful transmission logging with details
+
+**Admin UI Integration (1/6)**:
+- ❌ 4.4: Sensitive field masking (last 4 characters)
+
+**Error Handling & Security (3/7)**:
+- ❌ 5.1: Specific OAuth 2.0 error code logging
+- ❌ 5.2: Retry with exponential backoff (3 attempts)
+- ❌ 5.3: Failed email storage for manual review
+
+---
+
+## 4. Recommendations
+
+### Immediate Action Items (Pre-Production)
+
+1. **🔴 HIGH PRIORITY - Error Handling**: Implement retry logic with exponential backoff and detailed error logging (Est: 4-6 hours)
+   - Add `sendWithRetry()` wrapper in mail.ts
+   - Log OAuth 2.0 error codes and context
+   - Implement failed email storage mechanism
+
+2. **🔴 HIGH PRIORITY - Testing**: Add test coverage for critical paths (Est: 8-12 hours)
+   - Unit tests: mail.ts createOAuth2Client()
+   - Integration tests: API endpoints + email sending flow
+   - E2E test: Configure OAuth 2.0 + send test email
+
+3. **🟡 MEDIUM PRIORITY - Field Masking**: Implement credential masking in UI (Est: 2-3 hours)
+   - Display `****abcd` for saved secrets
+   - Allow full edit on focus
+
+### Future Enhancements (Post-Production)
+
+4. **🟡 MEDIUM PRIORITY - Test Email Support**: Verify and document OAuth 2.0 test email functionality (Est: 1-2 hours)
+
+5. **🟢 LOW PRIORITY - Help Text**: Add help text for all OAuth 2.0 fields (Est: 30 minutes)
+
+6. **🟢 LOW PRIORITY - Error Messages**: Enhance field-specific validation error messages (Est: 1-2 hours)
+
+### Risk Assessment
+
+| Risk | Likelihood | Impact | Mitigation |
+|------|-----------|--------|------------|
+| OAuth 2.0 token refresh fails in production | Medium | High | Implement error handling + monitoring alerts |
+| Admins misconfigure credentials | Medium | Medium | Add comprehensive help text + test email validation |
+| Transient network failures lose emails | Low | High | Implement retry logic + failed email queue |
+| Regression during future refactoring | High | Medium | Add test coverage before next release |
+
+---
+
+## 5. Conclusion
+
+The GitHub Copilot implementation delivers a **solid foundation** for OAuth 2.0 email support, with core functionality (configuration, authentication, email sending) working correctly. However, the implementation **lacks production-ready polish** in error handling, testing, and user experience refinements.
+
+### Go/No-Go Decision: ⚠️ **CONDITIONAL GO**
+
+**Recommendation**: Proceed to production with **immediate completion of High Priority items** (error handling + testing). The current implementation is functional for low-volume, non-critical email scenarios but requires hardening for production reliability.
+
+### Next Steps
+
+1. **Immediate** (before production deployment):
+   - Implement error handling with retry logic (4-6 hours)
+   - Add test coverage for critical paths (8-12 hours)
+
+2. **Short-term** (within 1-2 sprints):
+   - Implement field masking (2-3 hours)
+   - Verify test email support (1-2 hours)
+
+3. **Long-term** (future maintenance):
+   - Add comprehensive help text
+   - Enhance error messaging
+   - Monitor production OAuth 2.0 usage patterns
+
+### Alignment with Spec-Driven Development
+
+This implementation demonstrates the value of spec-driven development: the design document provided clear architectural guidance that Copilot followed effectively for **structural implementation**, while revealing that AI-generated code still requires **human oversight for production-quality concerns** like error handling, testing, and edge cases.
+
+**Design Quality**: ✅ Excellent (GO with minor recommendations)
+**Implementation Quality**: ⚠️ Good foundation, needs refinement (Conditional GO)
+**Overall Project Health**: 🟢 On track with clear remediation path
+
+---
+
+## Appendix: File Change Summary
+
+### Modified Files (12)
+
+1. `apps/app/src/server/service/config-manager/config-definition.ts` - Added 4 OAuth 2.0 config keys
+2. `apps/app/src/server/service/mail.ts` - Added createOAuth2Client() method
+3. `apps/app/src/server/routes/apiv3/app-settings/index.ts` - Added OAuth 2.0 API endpoints
+4. `apps/app/src/client/services/AdminAppContainer.js` - Added OAuth 2.0 state management
+5. `apps/app/src/client/components/Admin/App/MailSetting.tsx` - Added OAuth 2.0 option
+6. `apps/app/src/client/components/Admin/App/OAuth2Setting.tsx` - New component (created)
+7. `apps/app/src/interfaces/activity.ts` - Added ACTION_ADMIN_MAIL_OAUTH2_UPDATE
+8. `apps/app/public/static/locales/en_US/admin.json` - Added OAuth 2.0 translations
+9. `apps/app/public/static/locales/ja_JP/admin.json` - Added OAuth 2.0 translations
+10. `apps/app/public/static/locales/fr_FR/admin.json` - Added OAuth 2.0 translations
+11. `apps/app/public/static/locales/ko_KR/admin.json` - Added OAuth 2.0 translations
+12. `apps/app/public/static/locales/zh_CN/admin.json` - Added OAuth 2.0 translations
+
+### Lines of Code
+
+- **Total Added**: ~350 lines (estimated)
+- **Total Modified**: ~80 lines (estimated)
+- **Test Coverage**: 0 lines (🔴 critical gap)
+
+---
+
+**Report Generated**: 2026-02-06
+**Reviewer**: Claude Code (Sonnet 4.5)
+**Validation Framework**: Kiro Spec-Driven Development