# 通知設計書 1-conversation_creation

## 概要

本ドキュメントは、Chatwootシステムにおける「conversation_creation（新規会話作成通知）」の設計仕様を記載したものである。新しい会話が作成された際に、受信トレイのメンバー全員に通知を送信する機能の詳細を定義する。

### 本通知の処理概要

本通知は、顧客からの新規問い合わせが発生した際に、担当可能なエージェント全員に即座に通知することで、迅速な顧客対応を実現するための機能である。

**業務上の目的・背景**：カスタマーサポート業務において、顧客からの問い合わせに対する初動対応の速度は顧客満足度に直結する重要な要素である。本通知により、受信トレイに所属するすべてのエージェントが新規会話の発生を即座に把握し、迅速な対応を開始できる体制を構築する。特にチーム制でのサポート運用において、誰かが必ず対応できる状態を確保することが目的である。

**通知の送信タイミング**：会話（Conversation）レコードが新規作成され、`CONVERSATION_CREATED`イベントまたは`CONVERSATION_BOT_HANDOFF`イベントがディスパッチされた直後に送信される。ただし、会話のステータスが`pending`（ボット対応待ち）の場合は通知が送信されない。

**通知の受信者**：会話が作成された受信トレイ（Inbox）に所属するすべてのメンバー（エージェント）が受信者となる。ただし、各エージェントが通知購読設定で`email_conversation_creation`または`push_conversation_creation`を有効にしている場合のみ通知が作成される。

**通知内容の概要**：新規会話のID、受信トレイ名、会話の最初のメッセージ内容、および会話へのリンクが含まれる。

**期待されるアクション**：エージェントは通知を受け取った後、会話詳細ページにアクセスし、会話を自分にアサインするか、または即座に返信を開始することが期待される。

## 通知種別

- メール通知
- プッシュ通知（ブラウザプッシュ、FCMモバイルプッシュ）
- アプリ内通知（リアルタイム）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（ActiveJob） |
| 優先度 | 中（defaultキュー） |
| リトライ | Sidekiqデフォルト設定に準拠 |

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

1. 会話が作成された`Inbox`を特定
2. `Inbox#members`から受信トレイに所属する全エージェントを取得
3. 各エージェントに対して、`NotificationBuilder`を通じて通知を作成
4. ただし、`NotificationBuilder#user_subscribed_to_notification?`で購読状況をチェックし、メールまたはプッシュ通知を購読していないユーザーには通知を作成しない
5. コンタクトがブロックされている場合、通知は作成されない

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | 環境変数`MAILER_SENDER_EMAIL`（デフォルト: accounts@chatwoot.com） |
| 送信元名称 | Chatwoot（環境変数で設定可能） |
| 件名 | `{agent_name}, A new conversation [ID - {display_id}] has been created in {inbox_name}.` |
| 形式 | HTML（Liquidテンプレート） |

### 本文テンプレート

```html
<p>Hi {{user.available_name}}</p>

<p>
  A new conversation (<a href="{{ action_url }}">#{{conversation.display_id}}</a>) has been created in {{ inbox.name }}.
  <strong>{{ conversation.contact_name }}</strong> wrote:
</p>

{% for chat_message in conversation.recent_messages %}
  <div>
    <h4 style="margin: 0;">
      {% if chat_message.sender == user.available_name %}
        You
      {% else %}
        {{chat_message.sender}}
      {% endif %}
    </h4>
  </div>

  <div style="padding: 10px 20px; margin: 5px 0 20px 0; background: #F2F3F7; border-radius: 10px;">
    {% if chat_message.content %}
      {{chat_message.content}}
    {% endif %}

    {% if chat_message.attachments %}
      {% for attachment in chat_message.attachments %}
        Attachment [<a href="{{ attachment }}" _target="blank">Click here to view</a>]
      {% endfor %}
    {% endif %}
  </div>
{% endfor %}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | メール本文に添付ファイルへのリンクのみ含まれる |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| user.available_name | エージェントの表示名 | users.available_name | Yes |
| conversation.display_id | 会話の表示ID | conversations.display_id | Yes |
| inbox.name | 受信トレイ名 | inboxes.name | Yes |
| conversation.contact_name | コンタクト名 | contacts.name | Yes |
| conversation.recent_messages | 最新5件のメッセージ | messages (chat, last 5) | Yes |
| action_url | 会話詳細ページURL | 動的生成 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | CONVERSATION_CREATED | 会話ステータスがpending以外 | 新規会話作成時 |
| イベント | CONVERSATION_BOT_HANDOFF | 会話ステータスがpending以外 | ボットからのハンドオフ時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 会話ステータスがpending | ボット対応待ちの会話は通知しない |
| ユーザーが通知を購読していない | notification_settingsで無効の場合 |
| コンタクトがブロック済み | contacts.blockedがtrueの場合 |
| メール通知時、既読の場合 | notification.read_atが設定済みの場合はメール送信しない |
| ユーザーがメール未確認 | user.confirmed_atがnilの場合はメール送信しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[会話作成イベント発生] --> B{会話ステータス確認}
    B -->|pending| C[通知スキップ]
    B -->|pending以外| D[受信トレイメンバー取得]
    D --> E[各メンバーに対してループ]
    E --> F{通知購読確認}
    F -->|購読なし| G[スキップ]
    F -->|購読あり| H{コンタクトブロック確認}
    H -->|ブロック済み| G
    H -->|ブロックなし| I[Notification作成]
    I --> J[after_create_commit]
    J --> K{プッシュ購読?}
    K -->|Yes| L[PushNotificationJob]
    K -->|No| M{メール購読?}
    J --> M
    M -->|Yes| N[EmailNotificationJob]
    M -->|No| O[完了]
    L --> O
    N --> O
    C --> O
    G --> E
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| conversations | 会話情報取得 | primary_actor |
| inboxes | 受信トレイ情報 | メンバー取得用 |
| inbox_members | 受信トレイメンバー | 通知対象者特定 |
| users | エージェント情報 | 通知先 |
| notification_settings | 通知設定 | 購読状況確認 |
| contacts | コンタクト情報 | ブロック状態確認 |
| messages | メッセージ一覧 | テンプレート用 |

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

#### conversations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | 会話特定 | イベントデータから |
| display_id | 表示用ID | 通知本文用 |
| account_id | アカウント特定 | 通知作成用 |
| inbox_id | 受信トレイ特定 | メンバー取得用 |
| status | ステータス確認 | pending判定 |
| contact_id | コンタクト特定 | ブロック確認用 |

#### notification_settings

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email_flags | メール通知設定 | user_id, account_idで検索 |
| push_flags | プッシュ通知設定 | user_id, account_idで検索 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| notifications | INSERT | 通知レコード作成 |

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

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | notification_type | 1 (conversation_creation) | ENUM値 |
| INSERT | account_id | 会話のaccount_id | FK |
| INSERT | user_id | 通知先エージェントID | FK |
| INSERT | primary_actor_type | 'Conversation' | ポリモーフィック |
| INSERT | primary_actor_id | 会話ID | FK |
| INSERT | secondary_actor_type | 'User' or nil | 作成者 |
| INSERT | secondary_actor_id | Current.user.id or nil | 作成者ID |
| INSERT | last_activity_at | created_at | 作成時刻 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗（SMTP） | SMTPサーバー接続エラー | ログ出力、例外キャッチ |
| WebPush期限切れ | WebPush::ExpiredSubscription | サブスクリプション削除 |
| WebPush無効 | WebPush::InvalidSubscription | サブスクリプション削除 |
| WebPush認証エラー | WebPush::Unauthorized | サブスクリプション削除 |
| WebPushレート制限 | WebPush::TooManyRequests | ログ警告出力 |
| FCMエラー | レスポンスにerror含む | サブスクリプション削除 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（システムレベル） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。24時間送信可能。

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

- メール送信はSMTP over TLS/SSLを推奨
- プッシュ通知はVAPID認証を使用
- FCM通知はFirebase認証を使用
- 通知内容に個人情報（メッセージ内容）が含まれるため、HTTPS必須
- ユーザー確認済み（confirmed_at）のチェックにより、未確認アカウントへの送信を防止

## 備考

- `conversation_creation`通知は、通知購読設定で無効化されている場合、Notificationレコード自体が作成されない（他の通知タイプと異なる動作）
- ボットハンドオフ時にも同じ通知が送信される
- 重複通知は`Notification::RemoveDuplicateNotificationJob`で削除される

---

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

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

### 推奨読解順序

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

通知システムの中核となるデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | NOTIFICATION_TYPESの定義（38-47行目）、push_event_data（58-76行目） |
| 1-2 | notification_setting.rb | `app/models/notification_setting.rb` | FlagShihTzuによるビットフラグ管理（30-34行目） |
| 1-3 | notification_subscription.rb | `app/models/notification_subscription.rb` | プッシュ通知購読タイプ（23-26行目） |

**読解のコツ**: FlagShihTzuは単一カラムで複数のブール値フラグを管理するgemである。`email_flags`と`push_flags`は整数型だが、`email_conversation_creation?`のようなメソッドで個別フラグにアクセスできる。

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

処理の起点となるリスナーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_listener.rb | `app/listeners/notification_listener.rb` | conversation_created（16-28行目）、conversation_bot_handoff（2-14行目） |

**主要処理フロー**:
1. **4行目, 18行目**: `conversation.pending?`で会話ステータスをチェック
2. **6-12行目, 20-27行目**: `inbox.members.each`で受信トレイメンバーにループ
3. **7-12行目, 21-27行目**: `NotificationBuilder.new`で通知を構築

#### Step 3: 通知構築処理を理解する

NotificationBuilderの動作を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_builder.rb | `app/builders/notification_builder.rb` | user_subscribed_to_notification?（14-23行目）、build_notification（25-38行目） |

**主要処理フロー**:
- **27行目**: conversation_creation通知の場合のみ、購読チェックで通知作成をスキップ
- **29行目**: ブロックされたコンタクトの会話は通知しない（conversation_mention除く）
- **31-37行目**: 実際のNotificationレコード作成

#### Step 4: 通知配信処理を理解する

通知作成後のコールバック処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | notification.rb | `app/models/notification.rb` | process_notification_delivery（155-164行目）、user_subscribed_to_notification?（166-172行目） |
| 4-2 | push_notification_job.rb | `app/jobs/notification/push_notification_job.rb` | perform（4-6行目） |
| 4-3 | email_notification_job.rb | `app/jobs/notification/email_notification_job.rb` | perform（4-9行目） |

#### Step 5: プッシュ通知サービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | push_notification_service.rb | `app/services/notification/push_notification_service.rb` | perform（6-13行目）、send_browser_push（66-73行目）、send_fcm_push（90-100行目） |

#### Step 6: メール通知サービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | email_notification_service.rb | `app/services/notification/email_notification_service.rb` | perform（4-15行目） |
| 6-2 | conversation_notifications_mailer.rb | `app/mailers/agent_notifications/conversation_notifications_mailer.rb` | conversation_creation（2-11行目） |
| 6-3 | conversation_creation.liquid | `app/views/mailers/agent_notifications/conversation_notifications_mailer/conversation_creation.liquid` | メールテンプレート全体 |

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

```
Conversation#after_create_commit (notify_conversation_creation)
    |
    +-- dispatcher_dispatch(CONVERSATION_CREATED)
           |
           +-- NotificationListener#conversation_created
                  |
                  +-- conversation.inbox.members.each
                         |
                         +-- NotificationBuilder#perform
                                |
                                +-- user_subscribed_to_notification? (check)
                                |
                                +-- build_notification
                                       |
                                       +-- user.notifications.create!
                                              |
                                              +-- Notification#after_create_commit
                                                     |
                                                     +-- process_notification_delivery
                                                            |
                                                            +-- Notification::PushNotificationJob.perform_later
                                                            |      |
                                                            |      +-- PushNotificationService#perform
                                                            |             |
                                                            |             +-- send_browser_push
                                                            |             +-- send_fcm_push
                                                            |             +-- send_push_via_chatwoot_hub
                                                            |
                                                            +-- Notification::EmailNotificationJob.perform_later
                                                                   |
                                                                   +-- EmailNotificationService#perform
                                                                          |
                                                                          +-- ConversationNotificationsMailer#conversation_creation
```

### データフロー図

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

Conversation               NotificationListener                 Notification
(新規作成)        ───────▶  (イベントハンドリング)    ───────▶    (DB保存)
                                   │
                                   ▼
                           NotificationBuilder
                           (購読チェック・作成)
                                   │
                                   ▼
                           Notification Model
                           (after_create_commit)
                                   │
          ┌────────────────────────┼────────────────────────┐
          ▼                        ▼                        ▼
   PushNotificationJob     EmailNotificationJob     ActionCable
          │                        │                  (dispatch_create_event)
          ▼                        ▼                        │
   PushNotificationService EmailNotificationService         ▼
          │                        │                 WebSocket通知
          ▼                        ▼
   ブラウザ/FCMプッシュ    メール送信(SMTP)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification.rb | `app/models/notification.rb` | ソース | 通知モデル・配信トリガー |
| notification_listener.rb | `app/listeners/notification_listener.rb` | ソース | イベントリスナー |
| notification_builder.rb | `app/builders/notification_builder.rb` | ソース | 通知ビルダー |
| notification_setting.rb | `app/models/notification_setting.rb` | ソース | 通知設定モデル |
| push_notification_service.rb | `app/services/notification/push_notification_service.rb` | ソース | プッシュ通知サービス |
| email_notification_service.rb | `app/services/notification/email_notification_service.rb` | ソース | メール通知サービス |
| push_notification_job.rb | `app/jobs/notification/push_notification_job.rb` | ソース | プッシュ通知ジョブ |
| email_notification_job.rb | `app/jobs/notification/email_notification_job.rb` | ソース | メール通知ジョブ |
| conversation_notifications_mailer.rb | `app/mailers/agent_notifications/conversation_notifications_mailer.rb` | ソース | メーラー |
| conversation_creation.liquid | `app/views/mailers/agent_notifications/conversation_notifications_mailer/conversation_creation.liquid` | テンプレート | メールテンプレート |
| conversation.rb | `app/models/conversation.rb` | ソース | 会話モデル（イベント発火元） |
| inbox.rb | `app/models/inbox.rb` | ソース | 受信トレイモデル |
