# 通知設計書 128-rejection

## 概要

本ドキュメントは、GitLabにおける「メール拒否通知」メールの設計を記載します。受信メールの処理に失敗した場合に、元の送信者にエラー理由を通知するメールです。

### 本通知の処理概要

本通知は、GitLabのIncoming Email機能（メールでIssueやコメントを作成する機能）で、受信したメールの処理に失敗した場合に、元の送信者にエラー理由を通知するメール機能です。

**業務上の目的・背景**：GitLabはメールからIssueの作成やコメントの追加が可能です（Incoming Email機能）。しかし、様々な理由でメールの処理が失敗することがあります。例えば、宛先が不明、プロジェクトが見つからない、ユーザーに権限がない、メールが空である、などです。本通知は、メール送信者に処理が失敗した理由を伝え、Web UIからの操作を促すことで、ユーザー体験を向上させます。

**通知の送信タイミング**：`Gitlab::Email::FailureHandler.handle`が呼び出され、処理可能なエラーが発生した際に非同期で送信されます。

**通知の受信者**：元のメールの送信者（`@original_message.from`）に送信されます。

**通知内容の概要**：メールが処理できなかった理由と、Web UIから操作を行うよう促すメッセージが含まれます。

**期待されるアクション**：受信者は、エラー理由を確認し、Web UIからIssueやコメントを作成します。一部のエラー（空メール、バリデーションエラー）では、メールに返信することで再試行が可能な場合があります。

## 通知種別

メール（Email）

## 送信仕様

### 基本情報

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

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

元のメールの`From`ヘッダーから送信者メールアドレスを取得します。`From`ヘッダーがない場合は送信されません。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `Gitlab.config.gitlab.email_from` |
| 送信元名称 | `Gitlab.config.gitlab.email_display_name` |
| 件名 | `[Rejected] {元メールの件名}` |
| 形式 | HTML/テキスト（マルチパート） |

### 本文テンプレート

**HTML版:**
```html
<p>Unfortunately, your email message to GitLab could not be processed.</p>
{拒否理由（Markdown形式）}
```

**テキスト版:**
```
Unfortunately, your email message to GitLab could not be processed.

{拒否理由}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 本通知には添付ファイルはありません |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @reason | 拒否理由 | FailureHandlerで生成 | Yes |
| @original_message | 元のメール | Mail::Messageオブジェクト | Yes |
| @original_message.from | 元の送信者 | Fromヘッダー | Yes |
| @original_message.subject | 元の件名 | Subjectヘッダー | Yes |
| @original_message.message_id | 元のMessage-ID | Message-IDヘッダー | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| エラーハンドラー | `Gitlab::Email::FailureHandler.handle` | 処理可能なエラーが発生し、reasonが生成された | Incoming Emailの処理失敗 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `@original_message.from`がnil | 元のメールにFromヘッダーがない場合 |
| reasonがnil | 未知のエラーや処理対象外のエラーの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Incoming Email受信] --> B[メール処理実行]
    B --> C{処理成功?}
    C -->|Yes| D[正常終了]
    C -->|No| E[FailureHandler.handle呼び出し]
    E --> F{エラータイプ判定}
    F --> G[reason生成]
    G --> H{reason存在?}
    H -->|No| I[処理終了（通知なし）]
    H -->|Yes| J[receiver.mail.body = nil（セキュリティ対策）]
    J --> K{@original_message.from存在?}
    K -->|No| L[処理終了（return）]
    K -->|Yes| M[EmailRejectionMailer.rejection.deliver_later]
    M --> N[メール送信]
    N --> O[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| なし | - | メール処理のみでDBアクセスなし |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | メール送信のみで、DB更新は行わない |

## エラー処理

### エラーケース一覧（拒否理由）

| エラー種別 | 発生条件 | 拒否理由メッセージ | 再試行可能 |
|----------|---------|-----------------|----------|
| UnknownIncomingEmail | メールの宛先が不明 | We couldn't figure out what the email is for. Please create your issue or comment through the web interface. | No |
| SentNotificationNotFoundError | 返信先が見つからない | We couldn't figure out what the email is in reply to. Please create your comment through the web interface. | No |
| ProjectNotFound | プロジェクトが見つからない | We couldn't find the project. Please check if there's any typo. | No |
| EmptyEmailError | メールが空 | It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies. | Yes |
| UserNotFoundError | ユーザーが見つからない | We couldn't figure out what user corresponds to the email. Please create your comment through the web interface. | No |
| UserBlockedError | ユーザーがブロック | Your account has been blocked. If you believe this is in error, contact a staff member. | No |
| UserNotAuthorizedError | 権限がない | You are not allowed to perform this action. If you believe this is in error, contact a staff member. | No |
| NoteableNotFoundError | コメント対象が見つからない | The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member. | No |
| InvalidAttachment | 添付ファイルエラー | （エラーメッセージそのまま） | No |
| InvalidRecordError | バリデーションエラー | （エラーメッセージそのまま） | Yes |
| EmailTooLarge | メールサイズ超過 | We couldn't process your email because it is too large. Please create your issue or comment through the web interface. | No |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なネットワークエラー、SMTP接続エラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（エラー発生時に即座にキューイング）

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

- 元のメール本文は`receiver.mail.body = nil`でクリアされ、拒否メールには含まれない
- `Message-ID`、`In-Reply-To`、`References`ヘッダーを適切に設定し、メールスレッドを構成
- `can_retry`がtrueの場合のみ`Reply-To`ヘッダーを設定し、返信での再試行を許可
- ランダムなMessage-IDを生成してメールスプーフィングを防止

## 備考

- 拒否理由はMarkdown形式でレンダリングされる（HTML版）
- EE版では`render_if_exists 'shared/additional_email_text'`で追加テキストが表示される可能性がある
- `can_retry`フラグにより、一部のエラーでは返信による再試行が可能
- 元のメールへの返信として構成され、メールクライアントでスレッド表示される

---

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

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

### 推奨読解順序

#### Step 1: エラーハンドラーを理解する

通知のトリガーとなるFailureHandlerを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | failure_handler.rb | `lib/gitlab/email/failure_handler.rb` | `handle`メソッド、エラータイプごとのreason生成 |

**主要処理フロー**:
1. **行6-43**: `handle`メソッドの定義
2. **行8-34**: エラータイプに応じたreason生成（caseで分岐）
3. **行36-37**: `receiver.mail.body = nil`でセキュリティ対策
4. **行39**: `EmailRejectionMailer.rejection.deliver_later`で送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | email_rejection_mailer.rb | `app/mailers/email_rejection_mailer.rb` | `rejection`メソッド、ヘッダー設定 |

**主要処理フロー**:
1. **行8-26**: `rejection`メソッドの定義
2. **行9-10**: @reason, @original_message設定
3. **行12**: `@original_message.from`チェック
4. **行14-17**: 基本ヘッダー設定（to, subject）
5. **行19-21**: Message-ID, In-Reply-To, References設定
6. **行23**: `can_retry`の場合のみReply-To設定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | rejection.html.haml | `app/views/email_rejection_mailer/rejection.html.haml` | HTML版メールテンプレート |
| 3-2 | rejection.text.haml | `app/views/email_rejection_mailer/rejection.text.haml` | テキスト版メールテンプレート |

**主要処理フロー（HTML版）**:
- **行1-2**: 処理失敗のメッセージ
- **行4**: Markdown形式で拒否理由を表示

#### Step 4: テストから仕様を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | email_rejection_mailer_spec.rb | `spec/mailers/email_rejection_mailer_spec.rb` | テストケース |
| 4-2 | failure_handler_spec.rb | `spec/lib/gitlab/email/failure_handler_spec.rb` | FailureHandlerのテスト |

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

```
Incoming Email Receiver
    │
    └─ メール処理実行
           │
           └─ エラー発生
                  │
                  └─ Gitlab::Email::FailureHandler.handle(receiver, error)
                         │
                         ├─ エラータイプ判定
                         │      └─ reason生成（caseで分岐）
                         │
                         ├─ receiver.mail.body = nil（セキュリティ）
                         │
                         └─ EmailRejectionMailer
                                .rejection(reason, raw, can_retry)
                                .deliver_later
                                       │
                                       ├─ Mail::Message.new(raw)
                                       │
                                       ├─ ヘッダー設定
                                       │      ├─ Message-ID
                                       │      ├─ In-Reply-To
                                       │      ├─ References
                                       │      └─ Reply-To（can_retryの場合）
                                       │
                                       └─ mail_with_locale
                                              └─ テンプレートレンダリング
                                                     ├─ rejection.html.haml
                                                     └─ rejection.text.haml
```

### データフロー図

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

Incoming Email
(処理失敗)       ───▶  FailureHandler.handle
                              │
                              ▼
                        エラータイプ判定
                              │
                              ▼
                        reason生成
                              │
                              ▼
                        mail.body = nil
                        (セキュリティ)
                              │
                              ▼
                        EmailRejectionMailer
                        .rejection(reason, raw, can_retry)
                        .deliver_later    ───▶  Sidekiqキュー
                                                    │
                                                    ▼
                                               メール送信
                                               (元の送信者)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| failure_handler.rb | `lib/gitlab/email/failure_handler.rb` | ソース | エラーハンドラー |
| email_rejection_mailer.rb | `app/mailers/email_rejection_mailer.rb` | ソース | EmailRejectionメーラー |
| rejection.html.haml | `app/views/email_rejection_mailer/rejection.html.haml` | テンプレート | HTML版メールテンプレート |
| rejection.text.haml | `app/views/email_rejection_mailer/rejection.text.haml` | テンプレート | テキスト版メールテンプレート |
| email_rejection_mailer_spec.rb | `spec/mailers/email_rejection_mailer_spec.rb` | テスト | メーラーのテスト |
| failure_handler_spec.rb | `spec/lib/gitlab/email/failure_handler_spec.rb` | テスト | ハンドラーのテスト |
