# 通知設計書 86-access_requested_email

## 概要

本ドキュメントは、GitLabにおける「アクセスリクエスト通知（access_requested_email）」の設計を定義するものである。ユーザーがプロジェクトまたはグループへのアクセスをリクエストした際に、承認権限を持つメンバー（オーナー、メンテナー等）に通知メールを送信する。

### 本通知の処理概要

本通知は、ユーザーがプロジェクトまたはグループへのアクセス権をリクエストした際に、そのリクエストを承認できる権限を持つメンバーに対して通知メールを送信する機能である。

**業務上の目的・背景**：アクセスリクエストがあったことを承認権限者に迅速に通知し、リクエストへの対応を促す。これにより、新規メンバーの参加待ち時間を短縮し、コラボレーションの開始を加速する。また、承認者がリクエストを見落とすことを防止する。

**通知の送信タイミング**：アクセスリクエストが作成された直後（`Member#after_commit :send_request`）に送信される。リクエストはユーザーがプロジェクト/グループページから「Request access」ボタンをクリックした際に作成される。

**通知の受信者**：アクセスリクエストを承認できる権限を持つメンバー。`access_request_approvers_to_be_notified` メソッドで決定され、最大10人まで通知される。

**通知内容の概要**：リクエスト者の名前、リクエストされたアクセスレベル、プロジェクト/グループ名、およびメンバー管理ページへのリンクが含まれる。

**期待されるアクション**：受信者は通知を確認し、メンバー管理ページにアクセスしてリクエストを承認または拒否する。

## 通知種別

メール（テキスト形式）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqのデフォルトリトライ設定に従う |

### 送信先決定ロジック

1. `source.access_request_approvers_to_be_notified` で承認権限者のリストを取得
2. 最大10人（`ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT`）まで
3. 各承認者の `notification_email_for(member_source.notification_group)` でメールアドレスを取得

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | Gitlab.config.gitlab.email_from |
| 送信元名称 | Gitlab.config.gitlab.email_display_name |
| 件名 | `Request to join the {source_name} {source_type}` |
| 形式 | テキスト形式 |

### 本文テンプレート

```
{requester_name} ({requester_url}) requested {access_level} access to the {source_name} {source_type}.

{members_page_url}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| requester_name | リクエスト者の名前 | member.user.name | Yes |
| requester_url | リクエスト者のプロフィールURL | user_url(member.user) | Yes |
| access_level | リクエストされたアクセスレベル | member.human_access | Yes |
| source_name | プロジェクト/グループ名 | member_source.human_name | Yes |
| source_type | ソースタイプ（project/group） | member_source.model_name.singular | Yes |
| members_page_url | メンバー管理ページURL | polymorphic_url([member_source, "{source_type}_members".to_sym]) | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モデルコールバック | Member作成後（request状態） | notifiable?(:subscription) | after_commit :send_request |
| 画面操作 | アクセスリクエスト | notifiable?(:subscription) | プロジェクト/グループページからのリクエスト |
| API | アクセスリクエスト作成 | notifiable?(:subscription) | API経由でのリクエスト |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| member.blank? | Memberレコードが存在しない |
| notifiable?(:subscription) = false | 通知設定で無効化されている |
| importing状態 | インポート処理中 |
| 承認者が存在しない | access_request_approvers_to_be_notifiedが空 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[アクセスリクエスト作成] --> B{request?}
    B -->|No| C[送信スキップ]
    B -->|Yes| D{importing?}
    D -->|Yes| C
    D -->|No| E{notifiable?:subscription}
    E -->|No| C
    E -->|Yes| F[access_request_approvers取得]
    F --> G{承認者が存在?}
    G -->|No| C
    G -->|Yes| H[各承認者にメール送信]
    H --> I[AccessRequestedMailer.email]
    I --> J{valid_to_email?}
    J -->|No| K[ログ出力・スキップ]
    J -->|Yes| L[mail_with_locale]
    L --> M[deliver_later]
    M --> N[Sidekiqジョブキュー]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| members | リクエスト情報取得 | user_id, source_id, source_type, requested_at |
| users | ユーザー情報取得 | リクエスト者、承認者の情報 |
| projects | プロジェクト情報 | source_type='Project'の場合 |
| namespaces | グループ/名前空間情報 | source_type='Namespace'の場合 |
| notification_settings | 通知設定確認 | notifiable?判定用 |

### テーブル別参照項目詳細

#### members

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | メンバー識別 | paramsで渡される |
| user_id | リクエスト者ユーザーID | - |
| source_id | ソースID | - |
| source_type | ソースタイプ | - |
| access_level | リクエストされたアクセスレベル | - |
| requested_at | リクエスト日時 | NOT NULL（リクエスト状態の判定） |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| todos | INSERT | TodoService.create_member_access_request_todosで作成 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| メンバー未検出 | member.blank? | ログ出力してスキップ |
| SMTP接続エラー | メールサーバー接続失敗 | Sidekiqリトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | SMTPConnectionError等のネットワークエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Sidekiqキューの設定に依存 |
| 1日あたり上限 | 特に制限なし |

### 配信時間帯

特に制限なし（リクエスト作成時に即座に送信）

## セキュリティ考慮事項

- 受信者はアクセスリクエストを承認できる権限を持つメンバーのみ
- リクエスト者のプロフィールURLが含まれる
- メンバー管理ページへのリンクは認証が必要

## 備考

- メール送信と同時にTodoも作成される（`todo_service.create_member_access_request_todos`）
- 承認者の上限は `ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10`
- `target_type` はソースタイプ（project/group）を示す
- レイアウトは標準の `mailer` レイアウトを使用

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | member.rb | `app/models/member.rb` | request?メソッド（536-538行目）を確認 |

**読解のコツ**: `request?` は `requested_at.present?` を返す。リクエスト状態のメンバーは `requested_at` が設定されている。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | member.rb | `app/models/member.rb` | send_request（708-716行目）を確認 |

**主要処理フロー**:
1. **378行目**: after_commit :send_request, if: :request?
2. **708-716行目**: send_request - notifiable?チェックと各承認者へのメール送信

#### Step 3: メーラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | access_requested_mailer.rb | `app/mailers/members/access_requested_mailer.rb` | emailメソッド（11-18行目）を確認 |

**主要処理フロー**:
- **12行目**: valid_to_email?チェック
- **14-17行目**: mail_with_locale呼び出し

#### Step 4: テンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | email.text.erb | `app/views/members/access_requested_mailer/email.text.erb` | テキストテンプレートの内容確認 |

### プログラム呼び出し階層図

```
Member#after_commit (if: :request?)
    │
    └─ send_request
           │
           ├─ notifiable?(:subscription) チェック
           │
           ├─ source.access_request_approvers_to_be_notified
           │      │
           │      └─ 各承認者(最大10人)
           │
           ├─ Members::AccessRequestedMailer.with(member:, recipient:).email
           │      │
           │      ├─ valid_to_email? チェック
           │      │
           │      └─ mail_with_locale → deliver_later
           │
           └─ todo_service.create_member_access_request_todos
```

### データフロー図

```
[入力]                    [処理]                         [出力]

アクセスリクエスト ───▶ after_commit :send_request  ───▶ メール送信（複数）
    │                      │
    ├─ member              ├─▶ access_request_approvers取得
    │                      │      │
    │                      │      └─▶ 最大10人
    │                      │
    │                      ├─▶ 各承認者へメール送信
    │                      │
    │                      └─▶ Todo作成
    │
    └─ requested_at
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| member.rb | `app/models/member.rb` | ソース | Memberモデル、send_request定義 |
| access_requested_mailer.rb | `app/mailers/members/access_requested_mailer.rb` | ソース | アクセスリクエストメーラー |
| email.text.erb | `app/views/members/access_requested_mailer/email.text.erb` | テンプレート | テキスト形式メールテンプレート |
| todo_service.rb | `app/services/todo_service.rb` | ソース | Todo作成サービス |
