# 通知設計書 1-new_issue_email

## 概要

本ドキュメントは、GitLabにおける新規Issue作成時のメール通知機能（new_issue_email）の設計仕様を定義する。

### 本通知の処理概要

new_issue_emailは、GitLabプロジェクト内で新しいIssueが作成された際に、関連するユーザーに対してメール通知を送信する機能である。

**業務上の目的・背景**：プロジェクト管理において、新しいIssueの作成は重要なイベントである。チームメンバーがタイムリーに新しいタスクや課題を認識し、適切なアクションを取れるようにするため、自動的なメール通知が必要とされる。この通知により、チーム全体の情報共有が促進され、プロジェクトの進行管理が効率化される。

**通知の送信タイミング**：新しいIssueがWebUI、API、またはメール返信によって作成された直後に、非同期（Sidekiq経由）でメール送信がトリガーされる。NotificationService.new.new_issue(issue, current_user)が呼び出されることで処理が開始される。

**通知の受信者**：Issue担当者（assignee）、通知レベルがParticipating以上のプロジェクトメンバー、Issueのラベルをウォッチしているユーザー、カスタム通知レベルで「new issue」を有効にしているユーザーが受信対象となる。通知レベルがDisabledに設定されているユーザーは除外される。

**通知内容の概要**：Issue作成者名、Issueタイトル、Issue番号、担当者情報、Issue説明文（Markdown形式）が含まれる。機密Issueの場合はX-GitLab-ConfidentialIssueヘッダーでフラグが設定される。

**期待されるアクション**：受信者はIssueの内容を確認し、必要に応じてコメントの追加、担当の引き受け、優先度の設定などのアクションを行うことが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（Sidekiq deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqデフォルト設定（最大25回） |

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

NotificationRecipients::BuildServiceを使用して受信者を決定する。以下の順序で受信者が追加される：

1. Issueの参加者（participants）- :participating レベル
2. プロジェクトのウォッチャー - :watch レベル
3. カスタム通知設定ユーザー - :custom レベル
4. 担当者（assignees）- :participating レベル、NotificationReason::ASSIGNED
5. メンションされたユーザー - :mention レベル、NotificationReason::MENTIONED
6. ラベルの購読者 - :subscription レベル

Issue作成者本人は通知対象から除外される（skip_current_user: true）。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `{sender.name} ({sender.to_reference})` <gitlab@{host}> |
| 送信元名称 | 作成者名（@ユーザー名） |
| 件名 | `{project_name} | {issue_title} (#{iid})` |
| 形式 | HTML/テキスト両対応（multipart） |

### 本文テンプレート

```
{作成者名} created an issue: {issue_reference_link}

Assignee: {assignee_names} （担当者がいる場合）

{issue_description} （Markdown形式でレンダリング）
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @issue | Issueオブジェクト | Issue.find(issue_id) | Yes |
| @project | プロジェクトオブジェクト | @issue.project | Yes |
| @namespace | 名前空間 | @issue.namespace | Yes |
| @target_url | IssueへのURL | Gitlab::UrlBuilder.build(@issue) | Yes |
| @recipient | 受信者ユーザー | User.find(recipient_id) | Yes |
| @sent_notification | 送信通知記録 | SentNotification.record | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | Issue作成フォーム送信 | Issue.save成功 | WebUIからの新規Issue作成 |
| API | POST /api/v4/projects/:id/issues | Issue.save成功 | REST APIからの新規Issue作成 |
| メール返信 | Service Desk経由のメール | Issue.save成功 | メールからのIssue作成 |
| インポート | CSV/GitHubインポート | インポート成功 | バルクインポート時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| current_user.can_trigger_notifications? == false | 通知をトリガーできないユーザー（Bot等）からの作成 |
| project.emails_disabled? == true | プロジェクトでメール通知が無効化されている場合 |
| 受信者の通知レベルがDisabled | ユーザー設定で通知を無効化している場合 |
| レート制限超過 | プロジェクト/グループ単位の通知レート制限に達した場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Issue作成] --> B[NotificationService.new_issue]
    B --> C{current_user.can_trigger_notifications?}
    C -->|No| D[スキップ・ログ出力]
    C -->|Yes| E[NotificationRecipients::BuildService.build_recipients]
    E --> F[受信者リスト構築]
    F --> G[各受信者に対してループ]
    G --> H[Notify.new_issue_email.deliver_later]
    H --> I[Sidekiqキューに追加]
    I --> J[setup_issue_mail実行]
    J --> K[SentNotification.record]
    K --> L[mail_new_thread実行]
    L --> M{レート制限チェック}
    M -->|超過| N[送信スキップ・RateLimiterMailer送信]
    M -->|OK| O[メール送信]
    O --> P[終了]
    D --> P
    N --> P
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| issues | Issue情報取得 | メール本文データ |
| users | ユーザー情報取得 | 作成者・受信者・担当者 |
| projects | プロジェクト情報 | メール件名・ヘッダー |
| namespaces | 名前空間情報 | グループ情報取得 |
| notification_settings | 通知設定 | 受信者フィルタリング |
| labels | ラベル情報 | ラベル購読者取得 |
| members | メンバー情報 | プロジェクトメンバー取得 |

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

#### issues

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | Issue識別 | PRIMARY KEY |
| iid | Issue番号 | メール件名に使用 |
| title | タイトル | メール件名に使用 |
| description | 説明文 | メール本文に使用 |
| author_id | 作成者ID | 作成者情報取得 |
| project_id | プロジェクトID | プロジェクト情報取得 |
| namespace_id | 名前空間ID | グループ情報取得 |
| confidential | 機密フラグ | ヘッダー設定 |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別 | PRIMARY KEY |
| name | 表示名 | メール本文・ヘッダー |
| email | メールアドレス | 送信先アドレス |
| notification_email | 通知用メール | 送信先アドレス（優先） |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| p_sent_notifications | INSERT | 送信通知記録 |

#### 送信ログテーブル（p_sent_notifications）

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | project_id | @issue.project_id | プロジェクトID |
| INSERT | recipient_id | recipient_id | 受信者ID |
| INSERT | reply_key | SecureRandom生成 | 返信キー |
| INSERT | noteable_type | 'Issue' | 通知対象タイプ |
| INSERT | noteable_id | @issue.id | Issue ID |
| INSERT | namespace_id | @issue.namespace_id | 名前空間ID |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTP接続エラー | Sidekiqリトライ |
| テンプレートエラー | Hamlレンダリング失敗 | エラーログ出力 |
| 宛先不正 | メールアドレス形式不正 | 送信スキップ |
| Issue削除済み | 送信前にIssueが削除 | RecordNotFound例外 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Gitlab::ApplicationRateLimiter設定による |
| 1日あたり上限 | プロジェクト/グループ単位で設定 |

### 配信時間帯

制限なし（24時間送信可能）

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

- 機密Issue（confidential: true）の場合、X-GitLab-ConfidentialIssueヘッダーがtrueに設定される
- 受信者が当該Issueへのアクセス権限を持つかの確認はNotificationRecipientクラスで実施
- メール本文に含まれる個人情報（ユーザー名、メールアドレス）は必要最小限
- 返信キーは暗号学的に安全な乱数で生成（SecureRandom使用）

## 備考

- Issue作成者本人には通知が送信されない（skip_current_user設定）
- Work Itemsタイプのissueでも同様の通知が送信される
- メールのスレッド化のため、Message-IDとReferencesヘッダーが適切に設定される
- プロジェクトのList-Idヘッダーにより、メールクライアントでのフィルタリングが可能

---

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

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

### 推奨読解順序

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

まず、メール送信に必要なデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | issue.rb | `app/models/issue.rb` | Issueモデルの構造、belongs_to/has_many関連を確認（1-150行目） |
| 1-2 | sent_notification.rb | `app/models/sent_notification.rb` | 送信通知の記録方法、reply_keyの生成ロジック（68-127行目） |

**読解のコツ**: Railsのモデル定義では、belongs_to/has_manyの関連付けを先に把握することで、データ取得の流れが理解しやすくなる。

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

処理の起点となるNotificationServiceのnew_issueメソッドを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_service.rb | `app/services/notification_service.rb` | new_issueメソッド（209-211行目）がエントリーポイント |

**主要処理フロー**:
1. **209行目**: `def new_issue(issue, current_user)` - 公開メソッド定義
2. **210行目**: `new_resource_email(issue, current_user, :new_issue_email)` - 共通処理呼び出し
3. **805-816行目**: `new_resource_email`メソッドで受信者構築とメール送信実行

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

NotificationRecipients::BuildServiceによる受信者リスト構築処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | default.rb | `app/services/notification_recipients/builder/default.rb` | build!メソッド（27-58行目）で受信者追加順序を確認 |
| 3-2 | base.rb | `app/services/notification_recipients/builder/base.rb` | add_recipients、add_participants等のヘルパーメソッド（54-64行目、101-105行目） |

**主要処理フロー**:
- **28行目**: `add_participants(current_user)` - 参加者追加
- **29行目**: `add_watchers` - ウォッチャー追加
- **30行目**: `add_custom_notifications` - カスタム通知設定ユーザー追加
- **42行目**: `add_subscribed_users` - 購読者追加
- **48行目**: `add_mentions` - メンションユーザー追加
- **53行目**: `add_recipients(target.assignees, ...)` - 担当者追加

#### Step 4: メーラー実装を理解する

実際のメール生成処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | issues.rb | `app/mailers/emails/issues.rb` | new_issue_emailメソッド（5-16行目）、setup_issue_mail（183-192行目） |
| 4-2 | notify.rb | `app/mailers/notify.rb` | mail_new_thread（173-178行目）、mail_thread（135-159行目）でメールヘッダー設定 |

**主要処理フロー**:
- **5-16行目（issues.rb）**: new_issue_emailの処理フロー
- **183-192行目（issues.rb）**: setup_issue_mailでインスタンス変数設定
- **173-178行目（notify.rb）**: mail_new_threadでスレッド開始用ヘッダー設定
- **135-159行目（notify.rb）**: mail_threadで共通ヘッダー（X-GitLab-*）設定

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

メール本文のレンダリング処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | new_issue_email.html.haml | `app/views/notify/new_issue_email.html.haml` | HTML形式のメール本文テンプレート（全13行） |

**主要処理フロー**:
- **2-4行目**: 作成者名、Issue参照リンク、Work Itemタイプの表示
- **6-8行目**: 担当者表示（存在する場合）
- **10-12行目**: Issue説明文のMarkdownレンダリング

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

```
NotificationService#new_issue (notification_service.rb:209)
    │
    ├─ #new_resource_email (notification_service.rb:805)
    │      │
    │      ├─ current_user.can_trigger_notifications? チェック
    │      │
    │      ├─ NotificationRecipients::BuildService.build_recipients
    │      │      │
    │      │      └─ Builder::Default#build! (default.rb:27)
    │      │             ├─ #add_participants
    │      │             ├─ #add_watchers
    │      │             ├─ #add_custom_notifications
    │      │             ├─ #add_subscribed_users
    │      │             ├─ #add_mentions
    │      │             └─ #add_labels_subscribers
    │      │
    │      └─ recipients.each { Notify.new_issue_email.deliver_later }
    │
    └─ Notify#new_issue_email (emails/issues.rb:5)
           │
           ├─ #setup_issue_mail (emails/issues.rb:183)
           │      ├─ Issue.find
           │      ├─ User.find
           │      └─ SentNotification.record
           │
           └─ #mail_new_thread (notify.rb:173)
                  │
                  └─ #mail_thread (notify.rb:135)
                         ├─ #add_project_headers
                         ├─ #add_unsubscription_headers_and_links
                         ├─ #add_model_headers
                         └─ mail_with_locale
```

### データフロー図

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

Issue作成イベント ─────────▶ NotificationService ────────────▶ Sidekiqキュー
     │                            │                              │
     │                            ▼                              │
     │                     BuildService                          │
     │                     (受信者決定)                          │
     │                            │                              │
     │                            ▼                              │
     └─────────────────▶ Emails::Issues ─────────────────────────┤
                          #new_issue_email                       │
                                │                                │
                                ▼                                ▼
                        SentNotification ──────────▶ p_sent_notifications
                        (記録作成)                    テーブル
                                │
                                ▼
                        ActionMailer ────────────────▶ SMTPサーバー
                        (メール送信)                   └─▶ 受信者メールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | サービス | 通知処理のエントリーポイント |
| issues.rb | `app/mailers/emails/issues.rb` | メーラー | Issue関連メール送信処理 |
| notify.rb | `app/mailers/notify.rb` | メーラー | 共通メール処理・ヘッダー設定 |
| application_mailer.rb | `app/mailers/application_mailer.rb` | メーラー | メーラー基底クラス |
| default.rb | `app/services/notification_recipients/builder/default.rb` | サービス | 受信者構築ロジック |
| base.rb | `app/services/notification_recipients/builder/base.rb` | サービス | 受信者構築基底クラス |
| issue.rb | `app/models/issue.rb` | モデル | Issueデータモデル |
| sent_notification.rb | `app/models/sent_notification.rb` | モデル | 送信通知記録モデル |
| new_issue_email.html.haml | `app/views/notify/new_issue_email.html.haml` | テンプレート | HTML形式メール本文 |
