# 通知設計書 14-マイルストーン達成通知

## 概要

本ドキュメントは、Ghost CMSにおけるマイルストーン達成通知機能の設計仕様を記載したものである。

### 本通知の処理概要

マイルストーン達成通知は、サイトがARR（年間経常収益）または会員数の重要なマイルストーンに到達した際に、スタッフメンバーに対して祝福メールを送信する機能である。この通知により、サイト運営者は成長の節目を認識し、達成感を得ることができる。

**業務上の目的・背景**：サブスクリプションビジネスにおいて、ARRや会員数のマイルストーン達成は重要なビジネス成果を示す。$100、$1,000、$10,000などのARRマイルストーンや、100人、1,000人、10,000人などの会員数マイルストーンは、サイトの成長を可視化し、運営者のモチベーション向上に寄与する。Ghost公式のマイルストーン画像とカスタマイズされたメッセージにより、達成を祝福する。

**通知の送信タイミング**：`MilestoneCreatedEvent`ドメインイベントが発火され、かつ`emailSentAt`が設定されており、`meta.reason`が存在しない場合に送信される。マイルストーンサービスにより自動的に検出・トリガーされる。

**通知の受信者**：Ghostの管理画面で「マイルストーン達成通知」を有効化しているアクティブなスタッフメンバー全員。ユーザーモデルの`milestone_notifications`カラムが`true`かつ`status`が`active`のユーザーが対象。

**通知内容の概要**：マイルストーンの種類（ARR/会員数）に応じたカスタムの見出し、本文、祝福画像が含まれる。到達した金額または会員数、励ましのメッセージ、管理画面へのリンクが記載される。

**期待されるアクション**：受信者は達成を祝い、必要に応じて管理画面でさらなる詳細を確認できる。成長戦略の振り返りや次のマイルストーンに向けた計画立案に活用できる。

## 通知種別

メール通知（スタッフ向け）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（DomainEventsサブスクライバー経由）、Promise.allSettledで並列送信 |
| 優先度 | 中 |
| リトライ | 個別のsendMailの失敗はログに記録、全体処理は継続 |

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

`models.User.getEmailAlertUsers('milestone-received')` メソッドにより、以下の条件を満たすユーザーを取得：
- `status:active` - アクティブなユーザーのみ
- `milestone_notifications:true` - マイルストーン通知が有効（デフォルトはtrue）

該当する全ユーザーに対して並列でメールを送信する。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | サイト設定のデフォルトメールアドレス |
| 送信元名称 | サイト名 |
| 件名 | マイルストーンに応じて動的生成（例: `{サイト名} hit $1,000 ARR`） |
| 形式 | HTML/テキスト両対応 |

### 本文テンプレート

```html
<!-- HTMLテンプレート: new-milestone-received.hbs -->
<!-- Ghostのロゴアニメーションと祝福画像を含む特別なデザイン -->

<p style="font-size: 34px; font-weight: 800;">{{heading}}</p>

{{#each content}}
  <p>{{this}}</p>
{{/each}}

<a href="{{adminUrl}}">{{ctaText}}</a>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし（画像はURL参照） |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| subject | メール件名 | milestone-email-config | Yes |
| heading | メイン見出し | milestone-email-config | Yes |
| content | 本文（配列） | milestone-email-config | Yes |
| ctaText | CTAボタンテキスト | milestone-email-config | Yes |
| image.url | 祝福画像URL | milestone-email-config | Yes |
| image.height | 画像の高さ | milestone-email-config | No |
| partial | マイルストーン別パーシャル | `milestones/${milestone.value}` | Yes |
| adminUrl | 管理画面URL | urlUtils.urlFor('admin', true) | Yes |
| siteTitle | サイト名 | settingsCache.get('title') | Yes |
| siteUrl | サイトURL | urlUtils.getSiteUrl() | Yes |
| siteDomain | サイトドメイン | URLから抽出 | Yes |
| staffUrl | スタッフ設定URL | urlUtils.urlFor('admin') + '/settings/staff/{slug}/email-notifications' | Yes |
| toEmail | 受信者メールアドレス | user.email | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ドメインイベント | MilestoneCreatedEvent | milestone.emailSentAt が設定済み AND milestone.meta.reason が未設定 | マイルストーンが達成された際に発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| emailSentAtが未設定 | メール送信が設定されていない場合 |
| meta.reasonが存在 | 送信しない理由が設定されている場合 |
| emailDataが無効 | milestone-email-configに該当データがない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[MilestoneCreatedEvent発火] --> B{emailSentAt存在?}
    B -->|Yes| C{meta.reason存在?}
    B -->|No| Z[処理終了]
    C -->|No| D[マイルストーン設定読み込み]
    C -->|Yes| Z
    D --> E{emailData有効?}
    E -->|No| F[警告ログ出力]
    F --> Z
    E -->|Yes| G[通知対象ユーザー取得]
    G --> H[各ユーザーにメール送信（並列）]
    H --> I[Promise.allSettledで結果収集]
    I --> J{失敗あり?}
    J -->|Yes| K[失敗をログに記録]
    K --> Z
    J -->|No| Z
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| users | 通知対象スタッフ取得 | status:active, milestone_notifications:true |
| settings | サイト設定取得 | title |

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

#### milestone（イベントデータ）

| 参照項目 | 用途 | 取得条件 |
|---------|------|---------|
| type | マイルストーンタイプ（arr/members） | イベントから取得 |
| value | マイルストーン値（100, 1000等） | イベントから取得 |
| currency | 通貨（ARRの場合） | イベントから取得 |
| emailSentAt | メール送信日時 | 存在チェックに使用 |
| meta.reason | 送信しない理由 | 存在しないことをチェック |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | この通知では更新処理なし（マイルストーンサービス側で更新） |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| emailData無効 | milestone-email-configに該当データなし | logging.warnでログ出力、処理中断 |
| 送信失敗 | SMTP接続エラー等 | logging.warnでログ出力、他のユーザーへの送信は継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（Promise.allSettledで結果を収集するのみ） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（イベント発生時に即時送信）

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

- 収益情報（ARR）が含まれるため、送信先はスタッフメンバーのみに限定
- 画像URLは外部のGhost CDN（static.ghost.org）を参照

## 備考

### マイルストーン設定値

#### ARRマイルストーン
| 値 | 画像 |
|---|------|
| $100 | milestone-email-usd-100.png |
| $1,000 | milestone-email-usd-1000.png |
| $10,000 | milestone-email-usd-10k.png |
| $50,000 | milestone-email-usd-50k.png |
| $100,000 | milestone-email-usd-100k.png |
| $250,000 | milestone-email-usd-250k.png |
| $500,000 | milestone-email-usd-500k.png |
| $1,000,000 | milestone-email-usd-1m.png |

#### 会員数マイルストーン
| 値 | 特記事項 |
|---|------|
| 100 | 「最初の主要なマイルストーン」 |
| 1,000 | Kevin Kelly's「1000 True Fans」理論への言及 |
| 10,000 | 「Top 5%」のクリエイター |
| 25,000 | Madison Square Gardenを満席にできる規模 |
| 50,000 | Superbowlスタジアムをほぼ満席 |
| 100,000 | 米国最大のスタジアムを満席 |
| 250,000 | SXSWフェスティバルの規模、「Top 5%」 |
| 500,000 | 「Top 3%」のクリエイター |
| 1,000,000 | コパカバーナ、「Top 1%」 |

---

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

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

### 推奨読解順序

#### Step 1: マイルストーン設定データを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | milestone-email-config.js | `ghost/core/core/server/services/staff/milestone-email-config.js` | 全体: ARRと会員数のマイルストーン設定（subject, heading, content, ctaText, image） |

**読解のコツ**: `milestoneEmailConfig`関数は`siteTitle`と`formattedValue`を受け取り、各マイルストーンに対応するメールコンテンツを返す。`arr`と`members`の2種類のマイルストーンタイプがある。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | staff-service.js | `ghost/core/core/server/services/staff/staff-service.js` | 168-176行目: MilestoneCreatedEventサブスクリプション |
| 2-2 | staff-service.js | `ghost/core/core/server/services/staff/staff-service.js` | 81-84行目: handleEventでMilestoneCreatedEventの特別処理 |

**主要処理フロー**:
1. **168行目**: `MilestoneCreatedEvent`をサブスクライブ
2. **82-84行目**: 他のイベントとは異なり、ソースチェックなしで直接`notifyMilestoneReceived`を呼び出し

#### Step 3: メール送信処理

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | staff-service-emails.js | `ghost/core/core/server/services/staff/staff-service-emails.js` | 206-261行目: `notifyMilestoneReceived`メソッド |

**主要処理フロー**:
- **207-211行目**: emailSentAt/meta.reasonのチェック
- **213-214行目**: milestone-email-configから設定読み込み
- **216行目**: emailDataの取得（type + value）
- **224-252行目**: 各ユーザーへの並列送信
- **254-260行目**: Promise.allSettledで結果収集、失敗をログ

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

```
MilestoneCreatedEvent（ドメインイベント発火）
    │
    └─ StaffService.handleEvent()
           │
           └─ StaffServiceEmails.notifyMilestoneReceived()
                  │
                  ├─ milestone-email-config() - 設定読み込み
                  │      └─ milestoneEmailConfig[type][value]
                  │
                  ├─ models.User.getEmailAlertUsers('milestone-received')
                  │
                  ├─ getFormattedAmount() - マイルストーン値をフォーマット
                  │
                  ├─ renderEmailTemplate('new-milestone-received')
                  │
                  └─ Promise.allSettled(sendMail(...))
```

### データフロー図

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

MilestoneCreatedEvent ───▶ StaffService ───▶ スタッフメンバーへのメール
  │                           │
  ├─ milestone.type          ├─ 設定ファイル読み込み
  ├─ milestone.value         ├─ メールコンテンツ構築
  ├─ milestone.currency      ├─ 通知対象ユーザー取得
  ├─ milestone.emailSentAt   └─ 並列メール送信
  └─ milestone.meta.reason

[外部参照]
  └─ static.ghost.org - マイルストーン画像
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| staff-service.js | `ghost/core/core/server/services/staff/staff-service.js` | ソース | イベントハンドリング |
| staff-service-emails.js | `ghost/core/core/server/services/staff/staff-service-emails.js` | ソース | メール送信ロジック |
| milestone-email-config.js | `ghost/core/core/server/services/staff/milestone-email-config.js` | 設定 | マイルストーン別メールコンテンツ定義 |
| new-milestone-received.hbs | `ghost/core/core/server/services/staff/email-templates/new-milestone-received.hbs` | テンプレート | HTMLメールテンプレート |
| new-milestone-received.txt.js | `ghost/core/core/server/services/staff/email-templates/new-milestone-received.txt.js` | テンプレート | テキストメールテンプレート |
| milestone-created-event.js | `ghost/core/core/server/services/milestones/milestone-created-event.js` | ソース | MilestoneCreatedEventの定義 |
