# 機能設計書 158-利用説明メール送信

## 概要

本ドキュメントは、Chatwootのヘルプセンターにおける「利用説明メール送信」機能の設計仕様を定義する。この機能により、管理者はカスタムドメインのDNS設定（CNAMEレコード）に関する手順を担当者にメールで送信できる。

### 本機能の処理概要

**業務上の目的・背景**：
ヘルプセンターをカスタムドメインで公開するには、DNS設定（CNAMEレコードの追加）が必要である。この設定は通常、IT部門やドメイン管理者が行う。本機能により、Chatwoot管理者がDNS設定の手順を適切な担当者にメールで共有でき、設定プロセスがスムーズになる。

**機能の利用シーン**：
- 管理者がカスタムドメイン設定を完了し、IT部門にDNS設定を依頼する場面
- 外部のドメイン管理者にCNAME設定情報を共有する場面
- カスタムドメイン設定のトラブルシューティング時に設定情報を再送する場面

**主要な処理内容**：
1. メールアドレスのバリデーション
2. カスタムドメイン設定の確認
3. CNAMEレコード情報の生成
4. メール送信（非同期）

**関連システム・外部連携**：
- メール送信：ActionMailerによるメール配信
- 環境変数：HELPCENTER_URL、FRONTEND_URLからターゲットドメインを取得

**権限による制御**：
- メール送信：管理者（Administrator）権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 98 | ポータル設定画面 | 主画面 | 利用説明メールの送信 |

## 機能種別

メール送信 / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | String | Yes | ポータルのslug | Portal存在確認 |
| email | String | Yes | 送信先メールアドレス | 必須、メール形式検証 |

### 入力データソース

- URL パラメータ：ポータルのslug
- リクエストボディ：送信先メールアドレス
- API：`POST /api/v1/accounts/:account_id/portals/:id/send_instructions`

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| message | String | 成功メッセージ |

### 出力先

- JSON API レスポンス

## 処理フロー

### 処理シーケンス

```
1. メール送信
   └─ リクエスト受信 → ポータル取得 → 認可チェック → メールアドレス検証 → カスタムドメイン確認 → メール送信キュー追加 → レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[POST /portals/:id/send_instructions] --> B[fetch_portal]
    B --> C{Portalが存在?}
    C -->|No| D[404 Not Found]
    C -->|Yes| E[check_authorization]
    E --> F{emailが空?}
    F -->|Yes| G[422 email_required]
    F -->|No| H{メール形式が正しい?}
    H -->|No| I[422 invalid_email_format]
    H -->|Yes| J{custom_domain設定済み?}
    J -->|No| K[422 custom_domain_not_configured]
    J -->|Yes| L[PortalInstructionsMailer.deliver_later]
    L --> M[JSON: instructions_sent_successfully]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-158-01 | メールアドレス必須 | emailパラメータは必須 | send_instructions実行時 |
| BR-158-02 | メール形式検証 | ValidEmail2ライブラリでメール形式を検証 | send_instructions実行時 |
| BR-158-03 | カスタムドメイン必須 | カスタムドメインが設定されていない場合はエラー | send_instructions実行時 |
| BR-158-04 | 非同期送信 | メールはdeliver_laterで非同期送信 | メール送信時 |
| BR-158-05 | ターゲットドメイン優先順位 | HELPCENTER_URL > FRONTEND_URL | CNAMEレコード生成時 |

### 計算ロジック

```ruby
# CNAMEレコード生成
def generate_cname_record
  "#{@portal.custom_domain} CNAME #{target_domain}"
end

# ターゲットドメイン取得
def target_domain
  helpcenter_url = ENV.fetch('HELPCENTER_URL', '')
  frontend_url = ENV.fetch('FRONTEND_URL', '')

  return extract_hostname(helpcenter_url) if helpcenter_url.present?
  return extract_hostname(frontend_url) if frontend_url.present?

  ''
end

# ホスト名抽出
def extract_hostname(url)
  uri = URI.parse(url)
  uri.host
rescue URI::InvalidURIError
  url.gsub(%r{https?://}, '').split('/').first
end
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| send_instructions | portals | SELECT | ポータル情報取得 |

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

#### portals

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | custom_domain | - | CNAMEレコード生成に使用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | UnprocessableEntity | emailが未指定 | メールアドレスを指定 |
| 422 | UnprocessableEntity | メール形式が不正 | 正しいメール形式で指定 |
| 422 | UnprocessableEntity | custom_domainが未設定 | カスタムドメインを設定 |
| 404 | NotFound | 指定slugのPortalが存在しない | 正しいslugを指定 |
| 403 | Forbidden | 管理者権限がない | 管理者権限でアクセス |

### リトライ仕様

- メール送信失敗時はActiveJobのリトライ機構による再試行

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

- データベース更新なし
- メール送信は非同期ジョブとして実行

## パフォーマンス要件

- API レスポンス：100ms以内（メールは非同期送信のため即座に応答）

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

- 管理者権限チェック必須
- メールアドレスの検証（スパム防止）
- SMTP設定が有効な場合のみ送信

## 備考

- SMTP設定がない場合（開発環境除く）はメール送信されない
- target_domainが空の場合もメール送信されない

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | portals_controller.rb | `app/controllers/api/v1/accounts/portals_controller.rb` | send_instructionsアクション |

**主要処理フロー**:
- **49-60行目**: send_instructions - メール送信のエントリーポイント

```ruby
def send_instructions
  email = permitted_params[:email]
  return render_could_not_create_error(I18n.t('portals.send_instructions.email_required')) if email.blank?
  return render_could_not_create_error(I18n.t('portals.send_instructions.invalid_email_format')) unless valid_email?(email)
  return render_could_not_create_error(I18n.t('portals.send_instructions.custom_domain_not_configured')) if @portal.custom_domain.blank?

  PortalInstructionsMailer.send_cname_instructions(
    portal: @portal,
    recipient_email: email
  ).deliver_later

  render json: { message: I18n.t('portals.send_instructions.instructions_sent_successfully') }, status: :ok
end
```

- **106-108行目**: valid_email? - メール形式検証

```ruby
def valid_email?(email)
  ValidEmail2::Address.new(email).valid?
end
```

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

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

**主要処理フロー**:
- **2-12行目**: send_cname_instructions - メール送信メソッド

```ruby
def send_cname_instructions(portal:, recipient_email:)
  return unless smtp_config_set_or_development?
  return if target_domain.blank?

  @portal = portal
  @cname_record = generate_cname_record

  send_mail_with_liquid(
    to: recipient_email,
    subject: I18n.t('portals.send_instructions.subject', custom_domain: @portal.custom_domain)
  )
end
```

- **21-23行目**: generate_cname_record - CNAMEレコード生成
- **25-33行目**: target_domain - ターゲットドメイン取得

#### Step 3: 認可ポリシーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | portal_policy.rb | `app/policies/portal_policy.rb` | send_instructions?メソッド |

**主要処理フロー**:
- **30-32行目**: send_instructions? - 管理者のみ許可

```ruby
def send_instructions?
  @account_user.administrator?
end
```

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

```
Api::V1::Accounts::PortalsController#send_instructions
    │
    ├─ before_action :fetch_portal
    │      └─ Current.account.portals.find_by(slug:)
    │
    ├─ before_action :check_authorization
    │      └─ PortalPolicy#send_instructions?
    │
    ├─ バリデーション
    │      ├─ email.blank? → 422 email_required
    │      ├─ !valid_email?(email) → 422 invalid_email_format
    │      └─ custom_domain.blank? → 422 custom_domain_not_configured
    │
    └─ PortalInstructionsMailer.send_cname_instructions.deliver_later
           │
           ├─ smtp_config_set_or_development? チェック
           ├─ target_domain.blank? チェック
           ├─ generate_cname_record
           │      └─ "#{custom_domain} CNAME #{target_domain}"
           │
           └─ send_mail_with_liquid
```

### データフロー図

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

POST Request        →   send_instructions            →   JSON Response
{ email: "..." }         │                               { message: "..." }
                        ├─ バリデーション
                        │     ├─ メール形式検証
                        │     └─ custom_domain確認
                        │
                        └─ PortalInstructionsMailer
                               │
                               ├─ generate_cname_record
                               │     └─ ENV[HELPCENTER_URL/FRONTEND_URL]
                               │
                               └─ deliver_later
                                       ↓
                               [ActiveJob Queue]
                                       ↓
                               [SMTP送信]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| portals_controller.rb | `app/controllers/api/v1/accounts/portals_controller.rb` | コントローラー | send_instructionsアクション |
| portal_instructions_mailer.rb | `app/mailers/portal_instructions_mailer.rb` | メーラー | メール生成・送信 |
| portal_policy.rb | `app/policies/portal_policy.rb` | ポリシー | send_instructions?認可 |
| en.yml | `config/locales/en.yml` | 設定 | エラーメッセージ・件名の国際化 |
