# 機能設計書 24-メンバー歓迎メール

## 概要

本ドキュメントは、Ghostのメンバー歓迎メール機能の設計仕様を記述する。メンバー歓迎メール機能は、新規メンバー登録時に自動的に歓迎メールを送信し、サイトへのオンボーディングを促進する。

### 本機能の処理概要

**業務上の目的・背景**：新規メンバーの獲得後、最初のコミュニケーションは購読者との関係性構築において重要な役割を果たす。歓迎メールは、サイトの価値提案を伝え、コンテンツへの期待を高め、エンゲージメントの初期段階を確立する。この機能により、パブリッシャーはカスタマイズされた歓迎メッセージを無料/有料メンバーそれぞれに送信できる。

**機能の利用シーン**：
- 無料メンバー登録完了時の歓迎メール送信
- 有料メンバー登録完了時の歓迎メール送信
- 歓迎メールのテスト送信（プレビュー用）
- 歓迎メールの有効/無効切り替え

**主要な処理内容**：
1. メンバー登録イベントのハンドリング
2. メンバーステータス（free/paid）に応じたテンプレート選択
3. Lexicalコンテンツのメール用HTMLへのレンダリング
4. プレースホルダー（名前、メール等）の動的置換
5. GhostMailer経由でのメール送信

**関連システム・外部連携**：
- GhostMailer（メール送信）
- Lexical Renderer（コンテンツレンダリング）
- automated_emails テーブル（テンプレート管理）

**権限による制御**：管理者がテンプレートの編集・有効化/無効化を設定。送信は自動的にトリガーされる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 設定画面（メンバーシップ） | 主機能 | 歓迎メールの有効化・テンプレート編集 |

## 機能種別

自動メール送信 / テンプレートレンダリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| member | Object | Yes | メンバー情報（name, email） | email が必須 |
| memberStatus | string | Yes | メンバーステータス | 'free' または 'paid' |

### テストメール用パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email | string | Yes | 送信先メールアドレス | 有効なメールアドレス形式 |
| subject | string | Yes | メール件名 | 空でないこと |
| lexical | string | Yes | Lexical形式のコンテンツ | 有効なLexical JSON |
| automatedEmailId | string | Yes | 自動メールID | 存在するautomated_emailsのID |

### 入力データソース

- **automated_emails テーブル**: 歓迎メールテンプレート（slug: 'member-welcome-email-free' / 'member-welcome-email-paid'）
- **settings テーブル**: サイト設定（title, url, accent_color）
- **members テーブル**: メンバー情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| html | string | レンダリング済みHTMLメール本文 |
| text | string | プレーンテキスト版メール本文 |
| subject | string | プレースホルダー置換済み件名 |

### 出力先

- メールクライアント（GhostMailer経由）
- automated_email_recipients テーブル（送信履歴）

## 処理フロー

### 処理シーケンス

```
1. メンバー登録イベント受信
   └─ メンバーステータス（free/paid）を判定

2. 歓迎メールの有効性確認
   └─ automated_emails テーブルから該当テンプレートを取得
   └─ status が 'active' かつ lexical が存在することを確認

3. テンプレートのレンダリング
   └─ Lexicalコンテンツをメール用HTMLに変換
   └─ プレースホルダーの置換（first_name, name, email, site_title, site_url）

4. CSSインライン化
   └─ Juiceによるスタイルのインライン化

5. プレーンテキスト生成
   └─ HTMLからプレーンテキストへの変換

6. メール送信
   └─ GhostMailer.send()でメール送信
```

### フローチャート

```mermaid
flowchart TD
    A[メンバー登録イベント] --> B{歓迎メール有効?}
    B -->|No| C[終了]
    B -->|Yes| D[テンプレート取得]
    D --> E{テンプレート存在?}
    E -->|No| F[エラー: テンプレートなし]
    E -->|Yes| G{status = 'active'?}
    G -->|No| H[エラー: 非アクティブ]
    G -->|Yes| I[Lexicalレンダリング]
    I --> J[プレースホルダー置換]
    J --> K[CSSインライン化]
    K --> L[プレーンテキスト生成]
    L --> M[GhostMailer.send]
    M --> N[終了]
    F --> N
    H --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-24-01 | ステータス別テンプレート | free/paidそれぞれに異なるテンプレートを使用 | メンバー登録時 |
| BR-24-02 | 非アクティブスキップ | status が 'active' でないテンプレートは送信しない | 送信前チェック |
| BR-24-03 | プレースホルダーフォールバック | 値がない場合はフォールバック値を使用 | {first_name, "friend"} 形式 |
| BR-24-04 | テスト送信プレフィックス | テストメールの件名には [Test] プレフィックスを付与 | sendTestEmail時 |

### 計算ロジック

**first_name の抽出**：
- member.name から最初の単語を抽出
- 空白で分割して先頭を取得
- 名前がない場合は undefined を返却

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| テンプレート取得 | automated_emails | SELECT | slug でテンプレートを検索 |
| 有効性確認 | automated_emails | SELECT | status と lexical の存在確認 |
| テストメール | automated_emails | SELECT | id でテンプレートを検索（権限確認用） |

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

#### automated_emails

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | lexical, subject, status, sender_name, sender_email, sender_reply_to | slug = 'member-welcome-email-free' or 'member-welcome-email-paid' | テンプレート取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | IncorrectUsageError | テンプレートが存在しない | エラーメッセージを返却 |
| - | IncorrectUsageError | テンプレートが非アクティブ | エラーメッセージを返却 |
| - | IncorrectUsageError | 受信者メールアドレスがない | エラーメッセージを返却 |
| - | ValidationError | テストメール時にlexicalがない | エラーメッセージを返却 |
| - | ValidationError | テストメール時にsubjectがない | エラーメッセージを返却 |
| - | NotFoundError | テストメール時にautomated_emailが存在しない | エラーメッセージを返却 |
| - | IncorrectUsageError | Lexical構造が不正 | エラーメッセージを返却 |

### リトライ仕様

メール送信失敗時のリトライはGhostMailerの設定に依存。

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

読み取り専用のため、トランザクション管理は不要。

## パフォーマンス要件

- テンプレートレンダリング: 即時応答
- メール送信: 非同期処理

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

- テストメール時は automated_email の存在確認で権限チェック
- プレースホルダー置換時はHTML特殊文字をエスケープ
- テスト用インボックス設定で本番環境でのテストが可能

## 備考

- slug名: 'member-welcome-email-free', 'member-welcome-email-paid'
- テスト用インボックス: config.get('memberWelcomeEmailTestInbox') で設定可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | automated_emails テーブル（1139-1155行）のスキーマ定義 |
| 1-2 | constants.js | `ghost/core/core/server/services/member-welcome-emails/constants.js` | スラグ名やメッセージ定数 |

**読解のコツ**: automated_emails テーブルは status, slug, subject, lexical 等のカラムを持つ。slugが 'member-welcome-email-free' または 'member-welcome-email-paid' のレコードが歓迎メールテンプレート。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | service.js | `ghost/core/core/server/services/member-welcome-emails/service.js` | MemberWelcomeEmailService クラスがメインのサービスクラス |

**主要処理フロー**:
1. **32-49行目**: loadMemberWelcomeEmails() - テンプレートの読み込み
2. **52-96行目**: send() - メール送信のメイン処理
3. **98-107行目**: isMemberWelcomeEmailActive() - 有効性確認
4. **109-150行目**: sendTestEmail() - テストメール送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | member-welcome-email-renderer.js | `ghost/core/core/server/services/member-welcome-emails/member-welcome-email-renderer.js` | テンプレートレンダリングロジック |

**主要処理フロー**:
- **36-49行目**: #buildReplacementDefinitions() - プレースホルダー定義
- **62-77行目**: #applyReplacements() - プレースホルダー置換
- **88-118行目**: render() - メインのレンダリング処理

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

```
MemberWelcomeEmailService
    │
    ├─ loadMemberWelcomeEmails()
    │      └─ AutomatedEmail.findOne()
    │
    ├─ send({member, memberStatus})
    │      │
    │      ├─ #memberWelcomeEmails[memberStatus] 取得
    │      │
    │      ├─ MemberWelcomeEmailRenderer.render()
    │      │      ├─ lexicalLib.render()
    │      │      ├─ #applyReplacements()
    │      │      ├─ Handlebars.compile()
    │      │      ├─ juice() - CSSインライン化
    │      │      └─ htmlToPlaintext.email()
    │      │
    │      └─ GhostMailer.send()
    │
    ├─ isMemberWelcomeEmailActive()
    │      └─ AutomatedEmail.findOne()
    │
    └─ sendTestEmail()
           ├─ AutomatedEmail.findOne()
           ├─ MemberWelcomeEmailRenderer.render()
           └─ GhostMailer.send()
```

### データフロー図

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

member {name, email} ─────▶ send()
memberStatus ('free'/'paid')    │
                                │
                                ▼
automated_emails ──────────▶ テンプレート取得
  (slug, lexical, subject)      │
                                ▼
lexical content ───────────▶ lexicalLib.render() ─────────▶ HTML content
                                │
                                ▼
{name, email, site} ───────▶ #applyReplacements() ────────▶ 置換済みHTML
                                │
                                ▼
置換済みHTML ──────────────▶ juice() ─────────────────────▶ インライン化HTML
                                │
                                ▼
インライン化HTML ─────────▶ htmlToPlaintext() ────────────▶ プレーンテキスト
                                │
                                ▼
{html, text, subject} ────▶ GhostMailer.send() ───────────▶ メール送信
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| service.js | `ghost/core/core/server/services/member-welcome-emails/service.js` | ソース | メインサービスクラス |
| member-welcome-email-renderer.js | `ghost/core/core/server/services/member-welcome-emails/member-welcome-email-renderer.js` | ソース | レンダリングロジック |
| constants.js | `ghost/core/core/server/services/member-welcome-emails/constants.js` | ソース | 定数定義 |
| index.js | `ghost/core/core/server/services/member-welcome-emails/index.js` | ソース | モジュールエントリーポイント |
| wrapper.hbs | `ghost/core/core/server/services/member-welcome-emails/email-templates/wrapper.hbs` | テンプレート | HTMLラッパーテンプレート |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | DBスキーマ定義 |
