# 機能設計書 19-ニュースレター配信

## 概要

本ドキュメントは、Ghostにおけるニュースレター配信機能の設計仕様を記載する。本機能は、記事をニュースレターとしてメンバーに配信する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
サイト運営者がコンテンツを直接読者に届けるため、記事をメールとして配信する機能が必要である。ニュースレター配信機能により、記事の公開と同時に購読者へメール送信を行い、読者エンゲージメントを高めることができる。セグメント配信や予約配信にも対応し、効果的なコンテンツマーケティングを実現する。

**機能の利用シーン**：
- 新規記事を公開時にメール配信する場合
- 特定のセグメント（無料/有料会員）にのみ配信する場合
- 配信失敗時にリトライする場合
- テストメールを送信する場合

**主要な処理内容**：
1. 記事からEmailオブジェクトの作成
2. 購読者のセグメンテーション
3. バッチ分割と送信キュー登録
4. Mailgun APIを使用したメール送信
5. 配信ステータスの追跡

**関連システム・外部連携**：
- Mailgun API（メール送信）
- ニュースレター管理（配信対象ニュースレター）
- メンバー管理（購読者リスト）

**権限による制御**：
- ニュースレター配信: Author以上（記事公開権限に準ずる）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | エディタ画面 | 補助機能 | メール配信設定の指定 |

## 機能種別

バッチ処理 / 外部API連携

## 入力仕様

### 入力パラメータ（Email作成）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| post_id | string | Yes | 記事ID | 存在チェック |
| newsletter_id | string | Yes | ニュースレターID | 存在・active チェック |
| recipient_filter | string | No | 受信者フィルタ | NQL形式 |

### 入力データソース

- 記事公開処理からの内部呼び出し
- リトライ処理からの呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | EmailレコードID |
| post_id | string | 記事ID |
| newsletter_id | string | ニュースレターID |
| status | string | pending/submitting/submitted/failed |
| recipient_filter | string | 受信者フィルタ |
| subject | string | メール件名 |
| from | string | 送信元アドレス |
| replyTo | string | 返信先アドレス |
| email_count | number | 送信予定数 |
| delivered_count | number | 配信成功数 |
| opened_count | number | 開封数 |
| failed_count | number | 失敗数 |
| submitted_at | Date | 送信日時 |

### 出力先

- DBテーブル（emails, email_batches, email_recipients）
- Mailgun API（メール送信）

## 処理フロー

### 処理シーケンス

```
1. Email作成
   └─ 記事からEmailレコード作成、ステータス: pending
2. バッチ送信スケジュール
   └─ バックグラウンドジョブとして登録
3. ステータス更新: submitting
   └─ ロックを取得して排他制御
4. バッチ作成
   └─ 購読者をセグメント分割、EmailBatch作成
5. バッチ送信
   └─ 並行処理でMailgun APIへ送信
6. ステータス更新: submitted/failed
   └─ 全バッチ完了後にステータス更新
```

### フローチャート

```mermaid
flowchart TD
    A[記事公開] --> B{ニュースレター設定?}
    B -->|No| C[公開のみ]
    B -->|Yes| D[Emailレコード作成]
    D --> E[バックグラウンドジョブ登録]
    E --> F[ステータス: submitting]
    F --> G[バッチ作成]
    G --> H[購読者セグメンテーション]
    H --> I[EmailBatch作成]
    I --> J[並行送信 MAX_CONCURRENCY=2]
    J --> K{全バッチ成功?}
    K -->|Yes| L[ステータス: submitted]
    K -->|No| M[ステータス: failed]
    L --> N[完了]
    M --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-19-01 | アクティブニュースレター | archivedニュースレターには配信不可 | Email作成時 |
| BR-19-02 | 購読者制限 | email_disabled=trueのメンバーは除外 | バッチ作成時 |
| BR-19-03 | バッチサイズ | 1バッチあたりの最大受信者数はMailgun制限に準拠 | バッチ作成時 |
| BR-19-04 | 並行処理制限 | 最大2バッチを並行送信 | バッチ送信時 |
| BR-19-05 | リトライ機能 | 失敗したバッチはリトライ可能 | リトライ時 |
| BR-19-06 | ドメインウォーミング | カスタムドメインのウォーミング対応 | 送信時 |

### 計算ロジック

- バッチ数 = ceil(購読者数 / バッチサイズ)
- 配信時間分散: バッチを時間分散して配信

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| Email作成 | emails | INSERT | 配信レコード作成 |
| バッチ作成 | email_batches | INSERT | バッチ分割レコード |
| 受信者作成 | email_recipients | INSERT | 受信者レコード |
| ステータス更新 | emails | UPDATE | 配信ステータス更新 |
| バッチ更新 | email_batches | UPDATE | バッチステータス更新 |
| 受信者更新 | email_recipients | UPDATE | processed_at更新 |

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

#### emails

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | ObjectID | 自動生成 |
| INSERT | post_id | 記事ID | FK |
| INSERT | newsletter_id | ニュースレターID | FK |
| INSERT | status | pending | 初期値 |
| INSERT | submitted_at | 送信日時 | nullable |
| INSERT | subject | メール件名 | - |
| INSERT | from | 送信元 | - |
| INSERT | replyTo | 返信先 | - |
| INSERT | recipient_filter | 受信者フィルタ | - |
| INSERT | email_count | 送信予定数 | - |
| INSERT | track_opens | 開封トラッキング | boolean |
| INSERT | track_clicks | クリックトラッキング | boolean |
| INSERT | source | 記事ソース | lexical/mobiledoc |
| UPDATE | status | submitting/submitted/failed | 状態遷移 |
| UPDATE | error | エラーメッセージ | 失敗時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| EmailError | 500 | Mailgun API障害 | リトライ |
| BadRequestError | 400 | archivedニュースレター | activeに変更 |
| EmailError | 500 | 部分失敗 | 失敗バッチをリトライ |
| HostLimitError | 403 | メール上限超過 | プラン変更 |

### リトライ仕様

- DB操作: 自動リトライ（BEFORE_RETRY_CONFIG, AFTER_RETRY_CONFIG）
- Mailgun API: 自動リトライ（MAILGUN_API_RETRY_CONFIG）
- 手動リトライ: retryEmail() で再送信

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

- Email作成: 単一INSERT
- バッチ作成: トランザクション内でEmailBatch + EmailRecipients
- ステータス更新: FOR UPDATE ロックで排他制御

## パフォーマンス要件

- バッチサイズ: Mailgun制限に準拠
- 並行送信: 最大2バッチ同時
- 配信時間分散: ターゲットデリバリーウィンドウで分散

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

- Mailgun APIキーの安全な管理
- 購読者メールアドレスの適切な取り扱い
- 購読解除リンクの必須化

## 備考

- email_body_cache: セグメント別のHTMLボディをキャッシュ
- ドメインウォーミング: 新規カスタムドメイン用の段階的送信

---

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

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

### 推奨読解順序

#### Step 1: サービス全体構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | email-service.js | `ghost/core/core/server/services/email-service/email-service.js` | メインサービス |

**読解のコツ**:
- #プライベートフィールドで依存関係を管理
- createEmail()がエントリーポイント
- previewEmail(), sendTestEmail()でプレビュー/テスト送信

**主要処理フロー**:
- **84-102行目**: checkLimits() - 制限チェック
- **113-132行目**: checkCanSendEmail() - 送信可否事前チェック
- **139-184行目**: createEmail() - Emailレコード作成
- **186-205行目**: retryEmail() - リトライ処理
- **297-308行目**: previewEmail() - プレビュー
- **317-334行目**: sendTestEmail() - テスト送信

#### Step 2: バッチ送信を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | batch-sending-service.js | `ghost/core/core/server/services/email-service/batch-sending-service.js` | バッチ処理 |

**主要処理フロー**:
- **122-129行目**: scheduleEmail() - ジョブ登録
- **135-192行目**: emailJob() - ジョブ実行
- **199-219行目**: sendEmail() - メイン送信処理
- **239-342行目**: createBatches() - バッチ作成
- **430-480行目**: sendBatches() - バッチ送信
- **487-602行目**: sendBatch() - 個別バッチ送信

#### Step 3: レンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | email-renderer.js | `ghost/core/core/server/services/email-service/email-renderer.js` | HTML生成 |

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

```
EmailService
    │
    ├─ createEmail()
    │      ├─ checkCanSendEmail()
    │      ├─ models.Email.add()
    │      └─ batchSendingService.scheduleEmail()
    │
    └─ retryEmail()
           └─ batchSendingService.scheduleEmail()

BatchSendingService
    │
    ├─ scheduleEmail()
    │      └─ jobsService.addJob()
    │
    └─ emailJob()
           ├─ updateStatusLock() → submitting
           ├─ sendEmail()
           │      ├─ getBatches() or createBatches()
           │      └─ sendBatches()
           │             └─ sendBatch()
           │                    ├─ getBatchMembers()
           │                    └─ sendingService.send()
           │                           └─ Mailgun API
           └─ save() → submitted/failed
```

### データフロー図

```
[記事公開]              [EmailService]              [BatchSendingService]

Post ─────────────▶ createEmail()
                         │
                         ▼
                   Email INSERT
                   (status: pending)
                         │
                         ▼
                   scheduleEmail() ─────────────▶ emailJob()
                                                      │
                                                      ▼
                                               createBatches()
                                                      │
                                   ┌─────────────────┼─────────────────┐
                                   ▼                 ▼                 ▼
                             EmailBatch 1     EmailBatch 2     EmailBatch N
                                   │                 │                 │
                                   ▼                 ▼                 ▼
                              sendBatch()       sendBatch()       sendBatch()
                                   │                 │                 │
                                   └─────────────────┼─────────────────┘
                                                     ▼
                                               Mailgun API
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| email-service.js | `ghost/core/core/server/services/email-service/email-service.js` | ソース | メインサービス |
| batch-sending-service.js | `ghost/core/core/server/services/email-service/batch-sending-service.js` | ソース | バッチ処理 |
| email-renderer.js | `ghost/core/core/server/services/email-service/email-renderer.js` | ソース | HTML生成 |
| email-segmenter.js | `ghost/core/core/server/services/email-service/email-segmenter.js` | ソース | セグメント分割 |
| sending-service.js | `ghost/core/core/server/services/email-service/sending-service.js` | ソース | 送信処理 |
| mailgun-email-provider.js | `ghost/core/core/server/services/email-service/mailgun-email-provider.js` | ソース | Mailgun連携 |
| email-body-cache.js | `ghost/core/core/server/services/email-service/email-body-cache.js` | ソース | ボディキャッシュ |
