# 機能設計書 207-返信メール

## 概要

本ドキュメントは、Chatwootの返信メール機能について記載する。会話に対するエージェントの返信をメールで連絡先に送信するバックグラウンド処理機能である。

### 本機能の処理概要

返信メール機能は、エージェントが会話で返信したメッセージを、連絡先のメールアドレスに送信するバックグラウンドジョブ機能である。メールの形式は、最後の着信メッセージのタイプに応じて「サマリー付き返信」または「サマリーなし返信」が選択される。また、会話の全体履歴をトランスクリプトとして送信する機能も含む。

**業務上の目的・背景**：顧客がWeb WidgetやSNSチャネル以外の方法で問い合わせてくる場合、またはオフライン時に返信を受け取りたい場合、メール通知が重要となる。特にメールチャネルでの会話では、メール返信が主要なコミュニケーション手段となる。

**機能の利用シーン**：
- Web Widgetで開始された会話で、連絡先がオフラインになった場合
- メールチャネルでの会話への返信
- 会話履歴をメールで送信する場合

**主要な処理内容**：
1. 会話IDと最後のキューされたメッセージIDを受け取る
2. 最後の着信メッセージのタイプを判定
3. incoming_emailの場合はサマリーなし返信、それ以外はサマリー付き返信
4. メールをdeliver_laterで非同期送信
5. Redisからメーラーキーを削除

**関連システム・外部連携**：ActionMailer経由でSMTPサーバーに接続。Redisでメーラーキューを管理。

**権限による制御**：本機能はバックグラウンドジョブとして自動実行されるため、ユーザー権限による制御は適用されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | トリガー元 | エージェントによる返信操作 |

## 機能種別

バックグラウンド処理 / メール送信

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| conversation_id | Integer | Yes | 会話ID | 存在チェック |
| last_queued_id | Integer | Yes | 最後にキューされたメッセージID | - |

### 入力データソース

- conversationsテーブル（会話情報）
- messagesテーブル（メッセージ履歴）
- contactsテーブル（連絡先メールアドレス）
- inboxesテーブル（インボックス設定）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| メール | Email | 返信メール（HTMLまたはテキスト形式） |

### 出力先

- 連絡先のメールアドレス
- SMTPサーバー経由

## 処理フロー

### 処理シーケンス

```
1. ジョブ実行
   └─ ConversationReplyEmailJob.perform(conversation_id, last_queued_id)
2. 会話取得
   └─ Conversation.find(conversation_id)
3. 返信タイプ判定
   └─ 最後の着信メッセージがincoming_emailかどうか
4. メーラー呼び出し
   ├─ incoming_email → reply_without_summary
   └─ それ以外 → reply_with_summary
5. メール送信
   └─ deliver_later で非同期送信
6. キー削除
   └─ Redis::Alfred.delete(CONVERSATION_MAILER_KEY)
```

### フローチャート

```mermaid
flowchart TD
    A[ConversationReplyEmailJob開始] --> B[会話取得]
    B --> C{最後の着信メッセージ?}
    C -->|incoming_email| D[reply_without_summary]
    C -->|その他| E[reply_with_summary]
    D --> F[deliver_later]
    E --> F
    F --> G[Redisキー削除]
    G --> H[処理完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 返信タイプ判定 | incoming_emailの場合はサマリーなし、それ以外はサマリー付き | メール送信時 |
| BR-002 | SMTP必須 | SMTP設定がない場合はメール送信をスキップ | 常時 |
| BR-003 | 既読チェック | 連絡先が既にWidgetで確認済みの場合はメール送信をスキップ | サマリー付き/なし返信時 |
| BR-004 | メッセージフィルタ | email_reply_summarizable?なメッセージのみ対象 | サマリー付き返信時 |

### 計算ロジック

返信タイプ判定ロジック：
```ruby
if conversation.messages.incoming&.last&.content_type == 'incoming_email'
  ConversationReplyMailer.reply_without_summary(conversation, last_queued_id)
else
  ConversationReplyMailer.reply_with_summary(conversation, last_queued_id)
end
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話取得 | conversations | SELECT | 会話情報取得 |
| メッセージ取得 | messages | SELECT | 返信対象メッセージ取得 |
| 連絡先取得 | contacts | SELECT | メールアドレス取得 |
| インボックス取得 | inboxes | SELECT | メール設定取得 |

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

本機能はデータベースへの書き込みを行わない（参照のみ）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RecordNotFound | 会話が存在しない | ジョブ失敗 |
| - | SMTP接続エラー | SMTPサーバー接続失敗 | ActionMailerのリトライ機構 |

### リトライ仕様

ActiveJobのデフォルトリトライ動作に準拠。deliver_laterにより非同期実行。

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

参照のみのためトランザクション制御は不要。

## パフォーマンス要件

- キュー名：mailers
- メール送信は非同期（deliver_later）

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

- 会話内容がメールで送信されるため、機密情報の取り扱いに注意
- SMTP認証設定が適切に行われていること
- Reply-Toアドレスにはインバウンドメール用のUUIDが含まれる

## 備考

- ActionMailerのwithメソッドでアカウントを渡す
- Redisキーはメール重複送信防止に使用
- レイアウトは返信タイプに応じて切り替え（reply_without_summaryはレイアウトなし）

---

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

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

### 推奨読解順序

#### Step 1: ジョブ層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation_reply_email_job.rb | `app/jobs/conversation_reply_email_job.rb` | ジョブのエントリーポイントと返信タイプ判定 |

**読解のコツ**: シンプルなジョブクラス。performメソッドで返信タイプを判定してメーラーを呼び出す。

**主要処理フロー**:
1. **4行目**: performメソッドでconversation_idとlast_queued_idを受け取る
2. **5行目**: Conversation.findで会話取得
3. **7-11行目**: 返信タイプに応じてメーラーメソッドを呼び出し
4. **13行目**: Redisからメーラーキーを削除

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | conversation_reply_mailer.rb | `app/mailers/conversation_reply_mailer.rb` | メール生成ロジック |

**主要処理フロー**:
1. **11-22行目**: reply_with_summaryで過去メッセージを含むメール生成
2. **24-35行目**: reply_without_summaryで新規メッセージのみのメール生成
3. **37-43行目**: email_replyで個別メッセージのメール生成
4. **45-60行目**: conversation_transcriptで会話履歴全体のメール生成
5. **64-71行目**: init_conversation_attributesで会話関連オブジェクトを初期化

#### Step 3: メール構成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversation_reply_mailer.rb | `app/mailers/conversation_reply_mailer.rb` | 送信元、件名、CC/BCC設定 |

**主要処理フロー**:
- **73-83行目**: should_use_conversation_email_address?でインバウンドメール判定
- **89-96行目**: sender_nameでフレンドリー/プロフェッショナルな名前生成
- **110-124行目**: mail_subjectで件名生成（Re:プレフィックス処理含む）
- **126-132行目**: reply_emailで返信先アドレス生成

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

```
ConversationReplyEmailJob#perform(conversation_id, last_queued_id)
    │
    ├─ Conversation.find(conversation_id)
    │
    ├─ 返信タイプ判定
    │      └─ messages.incoming.last.content_type
    │
    ├─ ConversationReplyMailer.with(account: conversation.account)
    │      ├─ reply_with_summary(conversation, last_queued_id)
    │      │      ├─ init_conversation_attributes
    │      │      ├─ conversation_already_viewed?
    │      │      ├─ recap_messages + new_messages
    │      │      └─ prepare_mail(true)
    │      │
    │      └─ reply_without_summary(conversation, last_queued_id)
    │             ├─ init_conversation_attributes
    │             ├─ conversation_already_viewed?
    │             └─ prepare_mail(false)
    │
    └─ Redis::Alfred.delete(CONVERSATION_MAILER_KEY)
```

### データフロー図

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

conversation_id ───▶ ConversationReplyEmailJob ───▶ メール送信
last_queued_id            │
                     ┌────┴────┐
                     ▼         ▼
              会話取得    返信タイプ判定
                     │         │
                     └────┬────┘
                          ▼
                   ConversationReplyMailer
                          │
                     ┌────┴────┐
                     ▼         ▼
              メール構成   添付ファイル
                     │         │
                     └────┬────┘
                          ▼
                   deliver_later
                          │
                          ▼
                   SMTPサーバー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| conversation_reply_email_job.rb | `app/jobs/conversation_reply_email_job.rb` | ジョブ | メール送信ジョブ |
| conversation_reply_mailer.rb | `app/mailers/conversation_reply_mailer.rb` | メーラー | メール生成 |
| conversation_reply_mailer_helper.rb | `app/mailers/conversation_reply_mailer_helper.rb` | ヘルパー | メーラー補助 |
| conversation_reply_mailer_attachment_helper.rb | `app/mailers/conversation_reply_mailer_attachment_helper.rb` | ヘルパー | 添付ファイル処理 |
| references_header_builder.rb | `app/mailers/references_header_builder.rb` | モジュール | Referencesヘッダー生成 |
