# 通知設計書 4-ニュースレター登録通知

## 概要

本ドキュメントは、Payload CMSのexamples/emailサンプルプロジェクトにおけるニュースレター登録通知の設計仕様を記述する。ニュースレター登録時にウェルカムメールを送信するカスタム実装例の詳細を定義する。

### 本通知の処理概要

この通知は、ニュースレター登録コレクション（newsletter-signups）に新規登録があった際に、登録者にウェルカムメールを送信する機能を提供する。afterChangeフックを使用したカスタム通知実装のリファレンス実装であり、開発者が独自の通知ロジックを実装する際の参考となる。

**業務上の目的・背景**：ニュースレター購読者に対して登録完了の確認と歓迎メッセージを送信し、購読者のエンゲージメントを高めることが目的である。これにより、購読者は登録が正常に完了したことを確認でき、サービスへの信頼感が向上する。また、この実装パターンは、他のカスタム通知（注文確認、予約確認など）を実装する際のテンプレートとしても機能する。

**通知の送信タイミング**：newsletter-signupsコレクションに新規ドキュメントが作成された直後（afterChangeフック、operation === 'create'時）に送信される。更新時（operation !== 'create'）は送信されない。

**通知の受信者**：登録されたドキュメント（doc）のemailフィールドに設定されたメールアドレスに送信される。

**通知内容の概要**：ウェルカムメッセージと登録者の名前（存在する場合）を含むHTMLメール。EJSテンプレートとCSSインライン化を使用してスタイリングされたメールを生成する。

**期待されるアクション**：受信者は登録完了を確認し、今後のニュースレター配信を期待する。特別なアクション（確認リンクのクリックなど）は必要としない。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（fire-and-forget） |
| 優先度 | 低 |
| リトライ | なし（catch句でエラーログのみ出力） |

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

1. afterChangeフックで新規作成（operation === 'create'）を検出
2. 作成されたドキュメント（doc）からemailフィールドを取得
3. doc.emailをメール送信先として使用

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `sender@example.com`（ハードコード） |
| 送信元名称 | なし（アドレスのみ） |
| 件名 | `Thanks for signing up!` |
| 形式 | HTML |

### 本文テンプレート

```html
<p>${doc.name ? `Hi ${sanitizeUserDataForEmail(doc.name)}!` : 'Hi!'} We'll be in touch soon...</p>
```

上記コンテンツはgenerateEmailHTML関数により、EJSテンプレート（template.ejs）でラップされ、CSSがインライン化される。

EJSテンプレートの構造（想定）:
```html
<!DOCTYPE html>
<html>
<head>
  <style>
    /* スタイル定義 */
  </style>
</head>
<body>
  <h1><%= headline %></h1>
  <%= content %>
  <% if (cta && cta.url) { %>
    <a href="<%= cta.url %>"><%= cta.text %></a>
  <% } %>
</body>
</html>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| headline | メールの見出し | ハードコード: "Welcome to the newsletter!" | Yes |
| content | メール本文HTML | 動的生成（doc.name使用） | Yes |
| cta | CTAボタン情報（url, text） | オプション（この通知では未使用） | No |
| doc.name | 登録者名 | newsletter-signupsドキュメント | No |
| doc.email | 登録者メールアドレス | newsletter-signupsドキュメント | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API | POST `/api/newsletter-signups` | operation === 'create' | ニュースレター登録コレクションへの新規作成 |
| ローカルAPI | payload.create({collection: 'newsletter-signups'}) | 同上 | サーバーサイドでの新規作成 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| operation !== 'create' | 更新時はメール送信しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[newsletter-signupsドキュメント作成] --> B{operation === 'create'?}
    B -->|No| C[処理終了]
    B -->|Yes| D[doc.name確認]
    D --> E{doc.nameが存在?}
    E -->|Yes| F[名前付き挨拶文生成]
    E -->|No| G[汎用挨拶文生成]
    F --> H[sanitizeUserDataForEmailで名前をサニタイズ]
    H --> I[generateEmailHTML呼び出し]
    G --> I
    I --> J[EJSテンプレート読み込み]
    J --> K[テンプレートレンダリング]
    K --> L[CSSインライン化]
    L --> M[payload.sendEmail呼び出し]
    M --> N{送信成功?}
    N -->|Yes| O[正常終了]
    N -->|No| P[console.errorでログ出力]
    P --> O
    C --> Q[フック処理終了]
    O --> Q
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| newsletter-signups | 登録者情報取得 | afterChangeフックの引数として渡される |

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

#### newsletter-signups

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | 挨拶文のパーソナライズ | doc引数から直接アクセス |
| email | 送信先アドレス | doc引数から直接アクセス |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 送信通知処理ではDB更新なし |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | メールサーバー接続エラー | console.errorでログ出力、処理は継続 |
| テンプレートエラー | EJSテンプレートファイルが存在しない | generateEmailHTML内で例外発生 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Payload設定に依存（デフォルトなし） |
| 1日あたり上限 | Payload設定に依存（デフォルトなし） |

### 配信時間帯

制限なし（24時間送信可能）

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

1. **ユーザー入力のサニタイズ**: sanitizeUserDataForEmail関数を使用して、ユーザー名をHTMLエスケープ
2. **送信元アドレスのハードコード**: 設定ファイルや環境変数への移行を推奨
3. **非同期送信**: sendEmail().catch()パターンにより、メール送信失敗がユーザーリクエストに影響しない

## 備考

- この実装はexamples/emailプロジェクトのサンプルであり、本番環境では設定値の外部化が必要
- generateEmailHTML関数はEJSテンプレートエンジンとjuice（CSSインライン化）を使用
- sanitizeUserDataForEmail関数はpayload/sharedからインポートされる共通ユーティリティ
- CTAボタンなど追加のテンプレート機能は、generateEmailHTMLの引数で拡張可能

---

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

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

### 推奨読解順序

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

まず、newsletter-signupsコレクションの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Newsletter.ts | `examples/email/src/collections/Newsletter.ts` | コレクション定義、フィールド構造（11-21行目） |

**読解のコツ**: CollectionConfig型の構造を理解し、特にhooks.afterChange配列に注目する。フィールドはname（text、オプション）とemail（text、必須）の2つ。

#### Step 2: メール送信フックを理解する

afterChangeフックの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Newsletter.ts | `examples/email/src/collections/Newsletter.ts` | afterChangeフック（23-41行目） |

**主要処理フロー**:
1. **24行目**: フック引数から`doc`, `operation`, `req`を取得
2. **25行目**: `operation === 'create'`の条件チェック
3. **26-35行目**: req.payload.sendEmail呼び出し
4. **28行目**: 送信元アドレス設定（ハードコード）
5. **29-31行目**: generateEmailHTML呼び出し（content, headline）
6. **33行目**: 件名設定
7. **34行目**: 送信先設定（doc.email）
8. **36-38行目**: エラーハンドリング（.catch）

#### Step 3: HTMLテンプレート生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | generateEmailHTML.ts | `examples/email/src/email/generateEmailHTML.ts` | EJSテンプレート処理 |

**主要処理フロー**:
- **6-17行目**: async関数定義
- **7行目**: テンプレートパス生成（process.cwd() + 'src/email/template.ejs'）
- **8行目**: テンプレートファイル読み込み
- **11行目**: EJSレンダリング（data, cta引数使用）
- **14行目**: juice()でCSSインライン化
- **16行目**: HTML文字列を返却

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

```
newsletter-signups afterChange hook (Newsletter.ts:23-41)
    │
    ├─ operation === 'create' チェック
    │
    ├─ sanitizeUserDataForEmail (payload/shared)
    │      └─ doc.nameのHTMLエスケープ
    │
    ├─ generateEmailHTML (email/generateEmailHTML.ts)
    │      │
    │      ├─ fs.readFileSync (template.ejs読み込み)
    │      │
    │      ├─ ejs.render (テンプレートレンダリング)
    │      │
    │      └─ juice (CSSインライン化)
    │
    └─ req.payload.sendEmail
           │
           └─ .catch (エラーログ出力)
```

### データフロー図

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

newsletter-signups ───▶ afterChange hook ───▶ メール送信
ドキュメント                   │
   │                          │
   ├─ name ───────────────────┼─▶ sanitizeUserDataForEmail
   │                          │       └─▶ 挨拶文生成
   │                          │
   └─ email ──────────────────┼─▶ 送信先アドレス
                              │
                              └─▶ generateEmailHTML
                                      │
                                      ├─▶ template.ejs読み込み
                                      │
                                      ├─▶ EJSレンダリング
                                      │
                                      └─▶ CSSインライン化
                                              │
                                              └─▶ payload.sendEmail
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Newsletter.ts | `examples/email/src/collections/Newsletter.ts` | ソース | コレクション定義とafterChangeフック |
| generateEmailHTML.ts | `examples/email/src/email/generateEmailHTML.ts` | ソース | HTMLメール生成 |
| template.ejs | `examples/email/src/email/template.ejs` | テンプレート | EJSメールテンプレート |
| sanitizeUserDataForEmail | `packages/payload/src/utilities/sanitizeUserDataForEmail.ts`（推定） | ソース | ユーザー入力サニタイズ |
