# 通知設計書 3-new_mention_in_issue_email

## 概要

本ドキュメントは、GitLabにおけるIssue内メンション通知メール機能（new_mention_in_issue_email）の設計仕様を定義する。

### 本通知の処理概要

new_mention_in_issue_emailは、GitLabプロジェクト内のIssueのテキスト（説明文やコメント）が更新され、新たにユーザーがメンション（@username形式）された際に、メンションされたユーザーに対してメール通知を送信する機能である。

**業務上の目的・背景**：プロジェクトにおけるコミュニケーションでは、特定のメンバーに対して明示的に注意を喚起したい場合がある。@メンションによる通知機能は、タスクの割り当て、質問の回答依頼、レビューの依頼など、特定のユーザーへの働きかけを確実に伝達するために不可欠である。この通知により、重要な情報の見落としを防ぎ、チーム内のコミュニケーション効率が向上する。

**通知の送信タイミング**：Issueの説明文（description）が更新された際、または新しいコメントが追加された際に、新たにメンションされたユーザーが検出されると通知が送信される。NotificationService.new.new_mentions_in_issue(issue, new_mentioned_users, current_user)が呼び出されることで処理が開始される。

**通知の受信者**：Issue内で新たに@メンションされたユーザーが受信対象となる。既に以前のバージョンでメンションされていたユーザーは、重複通知を避けるため除外される。通知レベルがDisabledに設定されているユーザー、またはmentionレベル未満の設定をしているユーザーは除外される。

**通知内容の概要**：「You have been mentioned in an issue.」というメッセージに加え、Issue作成者名、Issueタイトル、Issue番号、担当者情報、Issue説明文（Markdown形式）が含まれる。

**期待されるアクション**：メンションされたユーザーはIssueの内容を確認し、必要に応じてコメントの追加、担当の引き受け、質問への回答などのアクションを行うことが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

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

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

NotificationRecipients::BuildServiceを使用して受信者を決定する。以下の特徴がある：

1. new_mentioned_usersリストに含まれるユーザーのみが対象
2. action: "new"で基本的な受信者リストを構築
3. 構築された受信者リストをnew_mentioned_usersでフィルタリング
4. NotificationReason::MENTIONEDが理由として設定される
5. 現在のユーザー（更新者）は通知対象から除外される（skip_current_user: true）

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```
You have been mentioned in an issue.

{作成者名} 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更新時 |
| 画面操作 | Issueコメントの投稿 | 新規メンションの検出 | コメント追加時 |
| API | PUT /api/v4/projects/:id/issues/:issue_iid | 新規メンションの検出 | REST API経由の更新 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| current_user.can_trigger_notifications? == false | 通知をトリガーできないユーザー（Bot等）からの更新 |
| project.emails_disabled? == true | プロジェクトでメール通知が無効化されている場合 |
| 受信者の通知レベルがmention未満 | ユーザー設定でメンション通知を無効化している場合 |
| 既にメンション済みのユーザー | 以前のバージョンで既にメンションされていた場合 |
| メンション者自身 | 自分自身をメンションした場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Issue更新] --> B[メンションユーザー抽出]
    B --> C[新規メンションの検出]
    C --> D{新規メンションあり?}
    D -->|No| E[終了]
    D -->|Yes| F[NotificationService.new_mentions_in_issue]
    F --> G{current_user.can_trigger_notifications?}
    G -->|No| H[スキップ・ログ出力]
    G -->|Yes| I[NotificationRecipients::BuildService.build_recipients]
    I --> J[受信者をnew_mentioned_usersでフィルタ]
    J --> K[各受信者に対してループ]
    K --> L[Notify.new_mention_in_issue_email.deliver_later]
    L --> M[Sidekiqキューに追加]
    M --> N[メール送信]
    N --> O[終了]
    H --> O
```

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

### 参照テーブル一覧

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

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

#### issues

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

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 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 |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- 機密Issue（confidential: true）の場合、X-GitLab-ConfidentialIssueヘッダーがtrueに設定される
- メンションされたユーザーが当該Issueへのアクセス権限を持つかの確認はNotificationRecipientクラスで実施
- アクセス権限のないユーザーへのメンション通知は送信されない

## 備考

- new_mention_in_issue_emailはmail_answer_threadを使用するため、既存のメールスレッドへの返信として送信される
- テンプレートはnew_issue_email.html.hamlをrenderで再利用し、冒頭に「You have been mentioned in an issue.」を追加
- updated_by_user_idがsender情報として使用される（Issue作成者ではなく更新者）
- 新規メンションの検出は、Issue更新前後のdescriptionを比較して行われる

---

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

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

### 推奨読解順序

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

まず、メンション検出に関連するデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | issue.rb | `app/models/issue.rb` | Issueモデルの構造、descriptionカラム |
| 1-2 | mentionable.rb | `app/models/concerns/mentionable.rb` | メンション検出ロジック（mentioned_users）|

**読解のコツ**: Mentionableモジュールのmentioned_usersメソッドが@usernameパターンを検出する核心ロジック。

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

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

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

**主要処理フロー**:
1. **217行目**: `def new_mentions_in_issue(issue, new_mentioned_users, current_user)` - 公開メソッド定義
2. **218-223行目**: new_mentions_in_resource_email呼び出し、:new_mention_in_issue_emailをmethod引数に指定

#### Step 3: 受信者フィルタリングロジックを理解する

new_mentions_in_resource_emailでのフィルタリング処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | new_mentions_in_resource_emailメソッド（818-830行目） |

**主要処理フロー**:
- **824行目**: BuildService.build_recipients(target, current_user, action: "new")で基本受信者リスト構築
- **825行目**: recipients.select { |r| new_mentioned_users.include?(r.user) }で新規メンションユーザーのみにフィルタ

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | issues.rb | `app/mailers/emails/issues.rb` | new_mention_in_issue_emailメソッド（24-34行目） |

**主要処理フロー**:
- **24行目**: `def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)` - 4引数
- **25行目**: setup_issue_mailでインスタンス変数設定
- **26-33行目**: mail_answer_threadでメール送信（updated_by_user_idがsender）

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | new_mention_in_issue_email.html.haml | `app/views/notify/new_mention_in_issue_email.html.haml` | メールテンプレート（全5行） |

**主要処理フロー**:
- **2行目**: "You have been mentioned in an issue."メッセージ表示
- **4行目**: `= render template: 'notify/new_issue_email'`でnew_issue_emailテンプレートを再利用

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

```
Issues::UpdateService / Notes::CreateService
    │
    └─ NotificationService#new_mentions_in_issue (notification_service.rb:217)
           │
           └─ #new_mentions_in_resource_email (notification_service.rb:818)
                  │
                  ├─ current_user.can_trigger_notifications? チェック
                  │
                  ├─ NotificationRecipients::BuildService.build_recipients
                  │      │
                  │      └─ Builder::Default#build! (action: "new")
                  │
                  ├─ recipients.select { |r| new_mentioned_users.include?(r.user) }
                  │
                  └─ recipients.each { Notify.new_mention_in_issue_email.deliver_later }
                         │
                         └─ Notify#new_mention_in_issue_email (emails/issues.rb:24)
                                │
                                ├─ #setup_issue_mail
                                │
                                └─ #mail_answer_thread (updated_by_user_id as sender)
```

### データフロー図

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

Issue更新イベント ──────────▶ Mentionable ───────────────────▶ new_mentioned_users
     │                      #mentioned_users                      │
     │                            │                               │
     │                            ▼                               │
     │                  NotificationService                       │
     │                  #new_mentions_in_issue                    │
     │                            │                               │
     │                            ▼                               │
     │                     BuildService ────────────────────────▶ Sidekiqキュー
     │                     (受信者フィルタ)                            │
     │                            │                               │
     └──────────────────▶ Emails::Issues ─────────────────────────┤
                          #new_mention_in_issue_email             │
                                │                                 │
                                ▼                                 ▼
                        SentNotification ──────────▶ p_sent_notifications
                        (記録作成)                    テーブル
                                │
                                ▼
                        ActionMailer ────────────────▶ SMTPサーバー
                        (メール送信)                   └─▶ メンションユーザーメールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | サービス | 通知処理のエントリーポイント（217-224行目、818-830行目） |
| issues.rb | `app/mailers/emails/issues.rb` | メーラー | new_mention_in_issue_emailメソッド定義（24-34行目） |
| notify.rb | `app/mailers/notify.rb` | メーラー | 共通メール処理・ヘッダー設定 |
| new_mention_in_issue_email.html.haml | `app/views/notify/new_mention_in_issue_email.html.haml` | テンプレート | HTML形式メール本文（全5行） |
| new_issue_email.html.haml | `app/views/notify/new_issue_email.html.haml` | テンプレート | 再利用されるベーステンプレート |
| mentionable.rb | `app/models/concerns/mentionable.rb` | Concern | メンション検出ロジック |
| issue.rb | `app/models/issue.rb` | モデル | Issueデータモデル |
| sent_notification.rb | `app/models/sent_notification.rb` | モデル | 送信通知記録モデル |
