# 通知設計書 85-access_granted_email

## 概要

本ドキュメントは、GitLabにおける「アクセス許可通知（access_granted_email）」の設計を定義するものである。プロジェクトまたはグループへのアクセス権が付与された際に、Members::AccessGrantedMailerを通じてメンバーに通知メールを送信する。

### 本通知の処理概要

本通知は、ユーザーがプロジェクトまたはグループのメンバーとしてアクセス権を付与された際に、そのユーザーに対してアクセス権付与を通知するメールを送信する機能である。新規のメンバー追加時およびアクセスレベル変更時に使用される。

**業務上の目的・背景**：チームメンバーがプロジェクトやグループへのアクセス権を得たことを明確に通知することで、メンバーが自身の役割と権限を認識できるようにする。これにより、プロジェクトへの参加開始を促し、コラボレーションの円滑な開始を支援する。Organization機能が有効な場合は、所属組織の情報も含まれる。

**通知の送信タイミング**：メンバーがプロジェクトまたはグループに追加された直後（post_create_member_hook）、もしくはアクセスレベルが変更された直後（post_update_hook）に送信される。招待の承諾やアクセスリクエストの承認後にも送信される。

**通知の受信者**：アクセス権を付与されたユーザー本人。通知設定で `:mention` レベルが無効化されている場合は送信されない。また、個人プロジェクトのオーナー自身には送信されない。

**通知内容の概要**：付与されたプロジェクト/グループの名前、付与されたロール（役割）、ロールタイプ、組織名（Feature flag有効時）、プロジェクト/グループへのリンク、および離脱用リンクが含まれる。

**期待されるアクション**：受信者は通知を確認し、付与されたアクセス権が正当であれば該当のプロジェクト/グループにアクセスして作業を開始する。不正または誤った付与の場合は、通知内のリンクから離脱するか管理者に連絡する。

## 通知種別

メール（テキスト形式）

## 送信仕様

### 基本情報

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

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

1. Memberレコードからuserを取得
2. `member.user.notification_email_for(notification_group)` でメールアドレスを取得
3. notification_group は、プロジェクトの場合はプロジェクトの親グループ、グループの場合はそのグループ自体

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

**Feature flag `ui_for_organizations` が有効な場合：**
```
You have been granted access to the {source_name} {source_type}, which is part of the {organization_name} organization with the following role: {role_name}.

{source_url}
{organization_url}
```

**Feature flag `ui_for_organizations` が無効な場合：**
```
You have been granted access to the {source_name} {source_type} with the following role: {role_name}.
This is a {role_type} role.

{source_url}

If this was a mistake you can leave the {source_type}.

{leave_url}
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| source_name | プロジェクト/グループ名 | member_source.human_name | Yes |
| source_type | ソースタイプ（project/group） | member_source.model_name.singular | Yes |
| role_name | 付与されたロール名 | member.present.human_access | Yes |
| role_type | ロールタイプ | member.present.role_type | Yes |
| source_url | プロジェクト/グループURL | member_source.web_url | Yes |
| leave_url | 離脱用URL | polymorphic_url([member_source], leave: 1) | Yes |
| organization_name | 組織名（Feature flag有効時） | member_source_organization.name | No |
| organization_url | 組織URL（Feature flag有効時） | member_source_organization.web_url | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モデルコールバック | Member作成後 | notifiable?(:mention)かつpending状態でない | post_create_member_hook |
| モデルコールバック | access_level変更後 | notifiable?(:mention) | post_update_hook |
| 画面操作 | 招待承諾 | notifiable?(:mention) | after_accept_invite |
| 画面操作 | アクセスリクエスト承認 | notifiable?(:mention) | after_accept_request |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| member.blank? | Memberレコードが存在しない |
| pending状態 | 招待中またはリクエスト中の場合（新規作成時） |
| notifiable?(:mention) = false | 通知設定で無効化されている |
| 個人プロジェクトのオーナー自身 | source.personal_namespace_holder?(user) |
| importing状態 | インポート処理中 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Member作成/更新] --> B{pending状態?}
    B -->|Yes| C[送信スキップ]
    B -->|No| D{importing?}
    D -->|Yes| C
    D -->|No| E{個人プロジェクトオーナー?}
    E -->|Yes| C
    E -->|No| F{notifiable?:mention}
    F -->|No| C
    F -->|Yes| G[send_access_granted_notification]
    G --> H[AccessGrantedMailer.with.email]
    H --> I[valid_to_email?チェック]
    I -->|No| J[ログ出力・終了]
    I -->|Yes| K[mail_with_locale]
    K --> L[deliver_later]
    L --> M[Sidekiqジョブキュー]
    M --> N[メール送信完了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| members | メンバー情報取得 | source_id, source_type, user_id, access_level |
| users | ユーザー情報取得 | 送信先メールアドレス |
| projects | プロジェクト情報 | source_type='Project'の場合 |
| namespaces | グループ/名前空間情報 | source_type='Namespace'の場合 |
| organizations | 組織情報 | Feature flag有効時 |
| notification_settings | 通知設定確認 | notifiable?判定用 |

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

#### members

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | メンバー識別 | paramsで渡される |
| source_id | ソースID | - |
| source_type | ソースタイプ | - |
| user_id | ユーザーID | - |
| access_level | アクセスレベル | ロール表示用 |

#### organizations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | 組織識別 | member_source.organization_id |
| name | 組織名 | 本文表示用 |
| path | 組織パス | URL生成用 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 本通知では更新処理なし |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

特に制限なし（トリガー発生時に即座に送信）

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

- 受信者は権限を付与されたユーザー本人のみ
- 離脱用リンクは認証が必要なページへのリンク
- プロジェクト/グループの詳細情報はメール本文に含まれない

## 備考

- Feature flag `ui_for_organizations` の有効/無効でテンプレートの内容が変わる
- アクセスレベル変更時も同じメーラーが使用される（send_access_level_updated_notification）
- params[:member_source_type] でソースタイプが渡される
- レイアウトは標準の `mailer` レイアウトを使用

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | member.rb | `app/models/member.rb` | Memberモデルの構造、sourceとの関連を確認 |

**読解のコツ**: `source` は polymorphic association であり、ProjectまたはNamespace（Group）のいずれかを指す。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | member.rb | `app/models/member.rb` | send_access_granted_notification（718-728行目）を確認 |
| 2-2 | member.rb | `app/models/member.rb` | post_create_member_hook（741-751行目）を確認 |
| 2-3 | member.rb | `app/models/member.rb` | post_update_hook（753-762行目）を確認 |

**主要処理フロー**:
1. **718-728行目**: send_access_granted_notification - notifiable_optionsの設定とメーラー呼び出し
2. **741-751行目**: post_create_member_hook - 新規作成時の通知トリガー
3. **753-762行目**: post_update_hook - 更新時の通知トリガー

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

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

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

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

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

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

```
Member#after_create / after_update
    │
    ├─ post_create_member_hook (after_create)
    │      │
    │      ├─ personal_namespace_holder? チェック
    │      │
    │      └─ send_access_granted_notification
    │
    └─ post_update_hook (after_update)
           │
           └─ send_access_level_updated_notification (if access_level changed)
                  │
                  └─ Members::AccessGrantedMailer.with(member:, member_source_type:).email
                         │
                         ├─ valid_to_email? チェック
                         │
                         ├─ notification_group 決定
                         │
                         └─ mail_with_locale → deliver_later
```

### データフロー図

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

Member作成/更新  ───▶ post_create/update_hook    ───▶ イベント記録
                       │
                       ▼
                  send_access_granted_notification
                       │
                       ├─▶ notifiable? チェック
                       │
                       └─▶ AccessGrantedMailer   ───▶ メール送信（Sidekiq）
                              │
                              ├─▶ member
                              ├─▶ member_source
                              ├─▶ member_source_organization
                              └─▶ member_source_type
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| member.rb | `app/models/member.rb` | ソース | Memberモデル、フック定義 |
| access_granted_mailer.rb | `app/mailers/members/access_granted_mailer.rb` | ソース | アクセス付与メーラー |
| email.text.erb | `app/views/members/access_granted_mailer/email.text.erb` | テンプレート | テキスト形式メールテンプレート |
