# 機能設計書 137-メール通知

## 概要

本ドキュメントは、GitLabにおけるメール通知機能の設計について記述する。ユーザーに対してプロジェクトやグループでの活動をメールで通知し、コラボレーションの促進とタイムリーな情報共有を実現する機能を定義する。

### 本機能の処理概要

**業務上の目的・背景**：ソフトウェア開発チームでは、イシュー、マージリクエスト、コメント等の活動をリアルタイムで把握することが重要である。メール通知機能により、ユーザーは自分に関連する活動を見逃すことなく、迅速な対応が可能になる。

**機能の利用シーン**：
- 新しいイシューが作成された時の通知
- マージリクエストの作成・更新・マージ時の通知
- コメント/ノートが追加された時の通知
- 担当者やレビュアーが変更された時の通知
- パイプラインの成功・失敗時の通知
- リリースが公開された時の通知
- アクセストークンの期限切れ警告

**主要な処理内容**：
1. 通知対象イベントの検知
2. 通知受信者の決定（NotificationRecipients）
3. メールテンプレートの選択とレンダリング
4. 非同期メール送信（Sidekiq経由）
5. 購読解除の処理

**関連システム・外部連携**：
- Sidekiq（非同期処理）
- SMTPサーバー
- GitLab Incoming Email（メール返信機能）

**権限による制御**：
- 通知レベルに基づく受信制御
- プロジェクト/グループ単位での通知設定
- カスタム通知イベントの選択

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | ユーザー設定 | 主機能 | 通知設定の管理 |
| 24 | プロジェクト詳細 | 補助機能 | プロジェクト通知設定 |
| 141 | グループ詳細 | 補助機能 | グループ通知設定 |

## 機能種別

メール送信 / 通知管理 / 購読管理 / 非同期処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| recipient_id | Integer | Yes | 受信者ユーザーID | - |
| target_id | Integer | Yes | 通知対象オブジェクトID | - |
| reason | String | No | 通知理由 | - |

### 入力データソース

- イベント発生元オブジェクト（Issue, MergeRequest, Note等）
- notification_settingsテーブルからの設定取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| to | String | 受信者メールアドレス |
| from | String | 送信者アドレス |
| subject | String | メール件名 |
| body | String | メール本文（HTML/Text） |
| headers | Hash | メールヘッダー（Message-ID等） |

### 出力先

- SMTPサーバー経由でのメール送信
- Sidekiqジョブキュー

## 処理フロー

### 処理シーケンス

```
1. イベント発生検知
   └─ NotificationService.new.{event_method}が呼ばれる
2. 非同期処理判定
   └─ asyncメソッド経由の場合はSidekiqにエンキュー
3. 受信者決定
   └─ NotificationRecipients::BuildServiceで受信者リスト生成
4. 通知可否チェック
   └─ notifiable?で通知レベル、権限をチェック
5. メール生成
   └─ Notifyクラスで該当メールテンプレートを使用
6. メール送信
   └─ deliver_laterで非同期送信
```

### フローチャート

```mermaid
flowchart TD
    A[イベント発生] --> B[NotificationService呼び出し]
    B --> C{非同期?}
    C -->|Yes| D[Sidekiqエンキュー]
    C -->|No| E[同期実行]
    D --> E
    E --> F[NotificationRecipients::BuildService]
    F --> G[受信者リスト生成]
    G --> H{各受信者}
    H --> I[notifiable?チェック]
    I -->|Yes| J[Notify.{method}]
    I -->|No| K[スキップ]
    J --> L[deliver_later]
    L --> M[Sidekiqジョブ作成]
    M --> N[メール送信]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-137-01 | 通知レベル制御 | 通知レベル設定に基づいてメール送信を制御 | 全通知 |
| BR-137-02 | 自己通知除外 | デフォルトで自分のアクションは通知しない | skip_current_user設定時 |
| BR-137-03 | receive_notifications権限 | 通知を受け取る権限がないユーザーは除外 | 全通知 |
| BR-137-04 | プロジェクト無効化 | プロジェクトでメールが無効な場合は送信しない | プロジェクト通知 |
| BR-137-05 | 非同期送信 | deliver_laterで非同期送信 | 全メール |

### 計算ロジック

- **通知レベル種別**: global(3), watch(2), participating(1), mention(4), disabled(0), custom(5)
- **カスタムイベント種別**: new_release, new_note, new_issue, reopen_issue, close_issue, reassign_issue, issue_due, new_merge_request, push_to_merge_request, reopen_merge_request, close_merge_request, reassign_merge_request, change_reviewer_merge_request, merge_merge_request, failed_pipeline, fixed_pipeline, success_pipeline, moved_project, merge_when_pipeline_succeeds

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 通知設定取得 | notification_settings | SELECT | ユーザーの通知レベル取得 |
| 送信通知記録 | sent_notifications | INSERT | 返信用キー生成・保存 |
| 受信者情報取得 | users | SELECT | メールアドレス取得 |
| メンバー情報取得 | members | SELECT | プロジェクト/グループメンバー取得 |

### テーブル別操作詳細

#### notification_settings

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user_id, source_type, source_id, level | ソースに応じた通知レベル | global/watch/participating/mention/disabled/custom |

#### sent_notifications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | recipient_id, noteable_type, noteable_id, reply_key | メール返信用情報 | partitioned_reply_keyで暗号化 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RecordNotFound | 対象オブジェクトが存在しない | ジョブをスキップ |
| - | SMTP Error | メール送信失敗 | Sidekiqリトライ |
| - | RateLimit | レート制限超過 | 送信抑制、通知メール送信 |

### リトライ仕様

- Sidekiqの標準リトライ機能を利用
- deliver_laterで非同期送信のため自動リトライ

## トランザクション仕様

- メール送信は非同期のためトランザクション外
- sent_notificationsレコード作成時のみトランザクション内

## パフォーマンス要件

- 非同期送信によりメイン処理をブロックしない
- 大量通知時はSidekiqでの分散処理
- レート制限機能（notification_emails）による過負荷防止

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

- 受信者の通知受信権限（can?(:receive_notifications)）を必ずチェック
- 機密イシューは専用ヘッダー（X-GitLab-ConfidentialIssue）で識別
- メール返信キーは暗号化して保存
- セキュリティメール（SSH鍵追加、2FA変更等）は通知設定に関わらず送信

## 備考

- Incoming Email機能有効時はメール返信でコメント追加が可能
- カスタム通知設定で個別イベントの有効/無効を制御可能
- グループ/プロジェクトごとに異なる通知メールアドレスを設定可能

---

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

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

### 推奨読解順序

#### Step 1: サービス層を理解する

メール通知の中核となるNotificationServiceを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification_service.rb | `app/services/notification_service.rb` | 通知サービスのメインロジック |

**読解のコツ（notification_service.rb）**:
- **18-47行目**: NotificationServiceクラスとAsyncクラスの定義、非同期処理の仕組み
- **49-60行目**: enabled_two_factorメソッド - 2FA有効化通知
- **84-88行目**: new_keyメソッド - SSH鍵追加通知（セキュリティメール）
- **209-211行目**: new_issueメソッド - 新規イシュー通知
- **282-284行目**: new_merge_requestメソッド - 新規MR通知
- **469-477行目**: new_noteメソッド - コメント通知
- **562-584行目**: pipeline_finishedメソッド - パイプライン完了通知
- **805-816行目**: new_resource_emailメソッド - 汎用リソース通知
- **878-880行目**: mailerメソッド - Notifyクラスの参照

#### Step 2: モデル層を理解する

通知設定のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_setting.rb | `app/models/notification_setting.rb` | 通知設定モデル |

**読解のコツ（notification_setting.rb）**:
- **7行目**: enum :level - 通知レベル定義（global, watch, participating, mention, disabled, custom）
- **38-58行目**: EMAIL_EVENTS - カスタム通知で選択可能なイベント一覧
- **60-66行目**: EXCLUDED_WATCHER_EVENTS - watchレベルで除外されるイベント
- **102-114行目**: failed_pipeline/fixed_pipeline - デフォルトで有効なイベント
- **116-122行目**: event_enabled?メソッド - イベント有効判定

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

メールテンプレートとヘッダー処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notify.rb | `app/mailers/notify.rb` | メインメーラークラス |
| 3-2 | issues.rb | `app/mailers/emails/issues.rb` | イシュー関連メール |

**読解のコツ（notify.rb）**:
- **11-30行目**: 各種メールモジュールのinclude
- **83-96行目**: senderメソッド - 送信者アドレスの生成
- **116-125行目**: subjectメソッド - 件名の生成
- **135-159行目**: mail_threadメソッド - スレッド対応ヘッダー設定
- **218-232行目**: add_model_headersメソッド - モデル識別ヘッダー
- **283-305行目**: check_rate_limitメソッド - レート制限処理

#### Step 4: 受信者決定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | build_service.rb | `app/services/notification_recipients/build_service.rb` | 受信者決定サービス |

**読解のコツ（build_service.rb）**:
- **8-10行目**: notifiable_usersメソッド - 通知可能ユーザーのフィルタ
- **16-18行目**: build_recipientsメソッド - 受信者リスト構築
- **20-21行目**: build_new_note_recipientsメソッド - ノート用受信者構築

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

```
NotificationService
    │
    ├─ async (Asyncクラス)
    │      └─ MailScheduler::NotificationServiceWorker.perform_async
    │
    ├─ new_issue / new_merge_request / new_note / etc.
    │      │
    │      ├─ current_user.can_trigger_notifications?
    │      │
    │      ├─ NotificationRecipients::BuildService.build_recipients
    │      │      └─ Builder::Default / Builder::NewNote / etc.
    │      │             └─ notification_recipients
    │      │                    └─ notifiable? チェック
    │      │
    │      └─ mailer.{method}(recipient, target, reason)
    │             └─ Notify.{method}
    │                    ├─ setup_mail (対象オブジェクト設定)
    │                    ├─ SentNotification.record (返信キー生成)
    │                    └─ mail_new_thread / mail_answer_thread
    │                           ├─ add_project_headers
    │                           ├─ add_model_headers
    │                           └─ mail_with_locale
    │                                  └─ deliver_later
    │                                         └─ Sidekiq Job
    │
    └─ mailer (Notifyクラス参照)

NotificationSetting
    │
    ├─ level (global/watch/participating/mention/disabled/custom)
    │
    ├─ EMAIL_EVENTS (カスタムイベント一覧)
    │
    └─ event_enabled? (イベント有効判定)
```

### データフロー図

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

イベント発生 ────────────────▶ NotificationService ──────────▶ Sidekiq Job
(Issue/MR/Note作成等)               │                              │
                                   ▼                              ▼
                          NotificationRecipients         MailScheduler Worker
                          ::BuildService                       │
                                   │                           ▼
                                   ▼                     Notify.deliver
                          notification_settings                │
                          (通知レベル確認)                      ▼
                                   │                    SMTP Server
                                   ▼                           │
                          受信者リスト生成                      ▼
                                   │                    メール配信
                                   ▼
                          Notify.{method}
                                   │
                                   ▼
                          SentNotification.record
                          (返信キー保存)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | サービス | 通知サービス本体 |
| notification_setting.rb | `app/models/notification_setting.rb` | モデル | 通知設定 |
| notify.rb | `app/mailers/notify.rb` | メーラー | メインメーラー |
| issues.rb | `app/mailers/emails/issues.rb` | メーラー | イシューメール |
| merge_requests.rb | `app/mailers/emails/merge_requests.rb` | メーラー | MRメール |
| notes.rb | `app/mailers/emails/notes.rb` | メーラー | コメントメール |
| profile.rb | `app/mailers/emails/profile.rb` | メーラー | プロファイルメール |
| pipelines.rb | `app/mailers/emails/pipelines.rb` | メーラー | パイプラインメール |
| build_service.rb | `app/services/notification_recipients/build_service.rb` | サービス | 受信者決定 |
| sent_notification.rb | `app/models/sent_notification.rb` | モデル | 送信済み通知 |
| notification_service_worker.rb | `app/workers/mail_scheduler/notification_service_worker.rb` | ワーカー | 非同期通知 |
