# 通知設計書 91-invite_accepted_email

## 概要

本ドキュメントは、GitLabにおける招待承諾通知（invite_accepted_email）の設計仕様を記述する。プロジェクトまたはグループへの招待がユーザーによって承諾された際に、招待者に対して送信されるメール通知の仕組みを定義する。

### 本通知の処理概要

**業務上の目的・背景**：プロジェクトやグループの管理者がユーザーを招待した場合、招待された側がその招待を承諾したかどうかを知ることは、チーム管理において重要である。この通知により、招待者は招待が正常に処理されたことを把握でき、チームメンバーの参加状況をリアルタイムで追跡できる。

**通知の送信タイミング**：ユーザーが招待リンクをクリックし、招待を承諾した直後にトリガーされる。具体的には、Memberモデルの`after_accept_invite`コールバックが実行されるタイミングで、データベーストランザクションのコミット後に非同期でメール送信がキューイングされる。

**通知の受信者**：招待を作成したユーザー（`member.created_by`）が受信者となる。招待者が存在しない場合やメンバーが無効な場合は通知は送信されない。また、プロジェクトへの招待の場合は、招待者のsubscription設定も考慮される。

**通知内容の概要**：招待されたメールアドレス、承諾したユーザーの名前、招待先のプロジェクトまたはグループの名前が含まれる。これにより招待者は誰が参加したかを明確に把握できる。

**期待されるアクション**：招待者は通知を受け取った後、必要に応じて新メンバーのロールや権限を確認・調整したり、新メンバーへのオンボーディング支援を行うことが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

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

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

1. `member.created_by`が存在するか確認
2. プロジェクトメンバーの場合、`member.notifiable?(:subscription)`を確認
3. 受信者の`notification_email_for(member_source.notification_group)`を取得

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabデフォルトアドレス |
| 送信元名称 | GitLab |
| 件名 | `[GitLab] Invitation accepted` |
| 形式 | HTML/テキスト |

### 本文テンプレート

```
{invite_email}, now known as {user_name}, has accepted your invitation
to join the {target_name} {target_model_name}.

{member_source.web_url}
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| invite_email | 招待されたメールアドレス | member.invite_email | Yes |
| user_name | 承諾したユーザーの名前 | member.user.name | Yes |
| target_name | 招待先の名前 | member_source.human_name | Yes |
| target_model_name | 招待先の種類（project/group） | member_source.model_name.singular | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モデルコールバック | 招待承諾（accept_invite） | member.created_by存在、notifiable確認 | Memberモデルのafter_accept_inviteコールバック |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| member.blank? | メンバーが無効な場合 |
| member.created_by.blank? | 招待者が存在しない場合 |
| プロジェクト招待でnotifiable?(:subscription)がfalse | プロジェクトメンバーで通知設定が無効な場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[招待リンクをクリック] --> B[accept_invite実行]
    B --> C[after_accept_inviteコールバック]
    C --> D{valid_to_email?チェック}
    D -->|Yes| E[InviteAcceptedMailer.email]
    D -->|No| F[送信スキップ]
    E --> G[deliver_later]
    G --> H[Sidekiqキューに追加]
    H --> I[非同期でメール送信]
    F --> J[終了]
    I --> J
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| members | メンバー情報の取得 | invite_email, created_by_id等 |
| users | ユーザー情報の取得 | 招待者、被招待者の情報 |
| projects | プロジェクト情報 | member_sourceがProjectの場合 |
| namespaces | グループ情報 | member_sourceがGroupの場合 |

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

#### members

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| invite_email | 招待されたメールアドレス | 直接参照 |
| created_by_id | 招待者ID | 直接参照 |
| source_id | 招待先ID | 直接参照 |
| source_type | 招待先種別 | 直接参照 |
| user_id | 承諾したユーザーID | 直接参照 |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | ユーザー名 | member.user経由 |
| notification_email | 通知先メールアドレス | member.created_by経由 |

### 更新テーブル一覧

なし（参照のみ）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPエラー | Sidekiqによるリトライ |
| メンバー不正 | member.blank? | ログ出力して送信スキップ |
| 招待者不在 | created_by.blank? | 送信スキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | SMTP接続エラー、タイムアウト |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし（個別通知） |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（リアルタイム送信）

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

- 招待者のみに通知を送信し、第三者への情報漏洩を防止
- メールアドレスはテンプレート内で直接表示されるため、HTMLエスケープ処理を適用

## 備考

- Members::InviteAcceptedMailerはApplicationMailerを継承
- run_after_commit_or_nowにより、トランザクションコミット後に確実に実行される

---

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

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

### 推奨読解順序

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

Memberモデルの構造と招待関連のカラムを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | member.rb | `app/models/member.rb` | invite_email, created_by, source関連の定義を確認 |

**読解のコツ**: Railsの多態的関連（polymorphic association）でsourceがProjectまたはGroupになる点に注意。

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

招待承諾時のコールバック処理の起点を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | member.rb | `app/models/member.rb` | after_accept_inviteメソッドの実装（795-801行目） |

**主要処理フロー**:
1. **795行目**: after_accept_inviteメソッド定義
2. **796行目**: run_after_commit_or_nowでトランザクション後にメール送信
3. **796行目**: Members::InviteAcceptedMailer.with(member: self).email.deliver_later

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

通知メールの構築ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | invite_accepted_mailer.rb | `app/mailers/members/invite_accepted_mailer.rb` | emailメソッドの実装、valid_to_email?のロジック |

**主要処理フロー**:
- **11-18行目**: emailメソッド - 送信条件チェックとメール送信
- **25-30行目**: body_textメソッド - メール本文テンプレート
- **36-45行目**: valid_to_email?メソッド - 送信可否判定

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

メール本文の構造を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | email.html.haml | `app/views/members/invite_accepted_mailer/email.html.haml` | HTML形式のテンプレート構造 |
| 4-2 | email.text.erb | `app/views/members/invite_accepted_mailer/email.text.erb` | テキスト形式のテンプレート |

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

```
Member#after_accept_invite (app/models/member.rb:795)
    │
    └─ run_after_commit_or_now
           │
           └─ Members::InviteAcceptedMailer.with(member: self).email
                  │
                  ├─ #valid_to_email? (送信可否判定)
                  │      ├─ member.blank? チェック
                  │      ├─ member.notifiable?(:subscription) チェック
                  │      └─ member.created_by.present? チェック
                  │
                  └─ mail_with_locale (ApplicationMailer)
                         │
                         └─ deliver_later (Sidekiq)
```

### データフロー図

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

Member#accept_invite  ───▶ after_accept_invite       ───▶ InviteAcceptedMailer
(招待承諾)                  (コールバック)                    │
                                                           ▼
member.invite_email   ───▶ body_text生成             ───▶ メール本文
member.user.name           (テンプレート展開)
member_source.name                                          │
                                                           ▼
                           mail_with_locale          ───▶ Sidekiqキュー
                           (メール組み立て)                   │
                                                           ▼
                                                      招待者へメール送信
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| member.rb | `app/models/member.rb` | ソース | メンバーモデル、招待承諾コールバック |
| invite_accepted_mailer.rb | `app/mailers/members/invite_accepted_mailer.rb` | ソース | メーラークラス |
| application_mailer.rb | `app/mailers/application_mailer.rb` | ソース | 基底メーラークラス |
| email.html.haml | `app/views/members/invite_accepted_mailer/email.html.haml` | テンプレート | HTML形式メール本文 |
| email.text.erb | `app/views/members/invite_accepted_mailer/email.text.erb` | テンプレート | テキスト形式メール本文 |
