# 通知設計書 18-スタッフ招待メール

## 概要

本ドキュメントは、Ghost CMSにおけるスタッフ招待メール機能の設計仕様を記載したものである。

### 本通知の処理概要

スタッフ招待メールは、管理者が新しいスタッフメンバーを招待する際に送信されるメールである。このメールには招待リンクが含まれており、受信者はそのリンクからGhost管理画面にアクセスしてアカウントを設定できる。

**業務上の目的・背景**：Ghostサイトの運営には複数のスタッフメンバー（管理者、エディター、著者、コントリビューター）が必要になることが多い。スタッフ招待機能は、既存の管理者が新しいチームメンバーを安全に招待し、適切なロールを割り当てるための仕組みを提供する。招待リンクはトークンベースでセキュアに設計されており、招待された本人のみがアカウントを作成できる。

**通知の送信タイミング**：管理者が管理画面のスタッフ設定からユーザーを招待した際、または招待APIが呼び出された際に同期的に送信される。既存の招待がある場合は削除してから新規作成する。

**通知の受信者**：招待されたメールアドレスの所有者。管理画面から指定されたメールアドレスに直接送信される。

**通知内容の概要**：招待者の名前またはメール、サイト名、サインアップ用の招待リンクが含まれる。招待リンクはトークンをBase64エンコードしたURLとなる。

**期待されるアクション**：受信者は招待リンクをクリックし、Ghost管理画面のサインアップページで自身のアカウント（名前、パスワード）を設定することが期待される。

## 通知種別

メール通知（管理者向け/招待メール）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（API呼び出し時に即座に送信） |
| 優先度 | 高 |
| リトライ | なし（エラー時は即座にエラーレスポンス） |

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

招待APIで指定されたメールアドレス（`invite.get('email')`）に直接送信する。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | 招待者のメールアドレス（replyTo） |
| 送信元名称 | システム |
| 件名 | `{招待者名} has invited you to join {サイト名}` または `You have been invited to join {サイト名}` |
| 形式 | HTML/テキスト両対応 |

### 本文テンプレート

招待テンプレートは`invite-user`または`invite-user-by-api-key`を使用（mailService.utils.generateContent経由）。

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| blogName | サイト名 | settingsCache.get('title') | Yes |
| invitedByName | 招待者の名前 | user.name | No |
| invitedByEmail | 招待者のメールアドレス | user.email | Yes |
| resetLink | サインアップURL | urlUtils.urlJoin(adminUrl, 'signup', Base64(token), '/') | Yes |
| recipientEmail | 受信者のメールアドレス | invite.get('email') | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | Invites.add() | 招待データが有効 | 管理画面またはAPIからスタッフ招待が実行された際 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| なし | 招待APIが呼ばれた場合は必ず送信を試みる |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Invites.add呼び出し] --> B{既存招待あり?}
    B -->|Yes| C[既存招待を削除]
    B -->|No| D[新規招待作成]
    C --> D
    D --> E[招待リンク生成]
    E --> F{招待者名あり?}
    F -->|Yes| G[invite-userテンプレート使用]
    F -->|No| H[invite-user-by-api-keyテンプレート使用]
    G --> I[メールコンテンツ生成]
    H --> I
    I --> J[件名生成]
    J --> K[api.mail.send実行]
    K --> L{送信成功?}
    L -->|Yes| M[招待ステータスを'sent'に更新]
    L -->|No| N[エラーログ出力]
    M --> O[招待オブジェクト返却]
    N --> P[エラーをreject]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| invites | 既存招待の確認・削除 | email でルックアップ |
| settings | サイト名取得 | title |
| users | 招待者情報（コンテキストユーザー） | name, email |

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

#### invites

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email | 既存招待確認 | 招待するメールアドレスで検索 |
| token | 招待リンク生成 | 新規作成時に自動生成 |
| status | 送信状態管理 | 送信成功時に'sent'に更新 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| invites | INSERT/DELETE/UPDATE | 招待の作成、既存削除、ステータス更新 |

#### 招待テーブル更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| DELETE | - | - | 同一メールの既存招待を削除 |
| INSERT | email, token, role_id, expires | 招待データ | 新規招待作成 |
| UPDATE | status | 'sent' | メール送信成功時 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| EmailError | SMTP送信失敗 | エラーメッセージにヘルプテキスト追加、logging.warn |
| その他エラー | 予期しないエラー | そのままreject |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（API呼び出し時に即時送信）

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

- 招待トークンはセキュアに生成され、Base64エンコードしてURLに含まれる
- 招待リンクは一度のみ使用可能（シングルユースとして設計されるべき）
- 招待者のメールアドレスがreplyToに設定され、なりすましを防止
- API経由の招待では招待者名が不明のため、別テンプレートを使用

## 備考

- 同一メールアドレスへの重複招待は、既存招待を削除してから新規作成
- 招待者の名前がない場合（API経由など）は件名が変化
- `invite-user-by-api-key`テンプレートでは招待者メールがサイトのデフォルトアドレスになる
- トークンはsecurity.url.encodeBase64でエンコード

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 22-104行目: `add`メソッド全体 |

**読解のコツ**: Promiseチェーンで処理が流れる。`.then()`を追うことで処理順序を把握できる。

#### Step 2: 処理フローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 26-33行目: 既存招待の確認と削除 |
| 2-2 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 34-36行目: 新規招待の作成 |
| 2-3 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 37-61行目: emailDataの構築とテンプレート選択 |

**主要処理フロー**:
- **26-33行目**: 既存招待があれば削除
- **35行目**: InviteModel.addで新規作成
- **40行目**: adminUrlを取得
- **42-49行目**: 招待者名がある場合のemailData
- **52-58行目**: 招待者名がない場合（API経由）のemailData
- **47, 56行目**: resetLinkの生成（Base64エンコード）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 62-81行目: メールコンテンツ生成と送信 |
| 3-2 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 84-90行目: 招待ステータスの更新 |
| 3-3 | invites.js | `ghost/core/core/server/services/invites/invites.js` | 92-102行目: エラーハンドリング |

**主要処理フロー**:
- **64-80行目**: payloadの構築（to, replyTo, subject, html, text）
- **69-74行目**: 件名の生成（招待者名の有無で分岐）
- **82行目**: api.mail.sendでメール送信
- **85-87行目**: ステータスを'sent'に更新
- **92-99行目**: EmailErrorの場合はヘルプテキスト追加

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

```
Invites.add(api, InviteModel, invites, options, user)
    │
    ├─ InviteModel.findOne({email}) - 既存招待確認
    │      └─ existingInvite.destroy() - 既存削除
    │
    ├─ InviteModel.add(invites[0]) - 新規招待作成
    │
    ├─ security.url.encodeBase64(invite.get('token')) - トークンエンコード
    │
    ├─ urlUtils.urlJoin(adminUrl, 'signup', resetToken, '/') - リンク生成
    │
    ├─ mailService.utils.generateContent({data, template}) - メールコンテンツ生成
    │      └─ invite-user または invite-user-by-api-key テンプレート
    │
    ├─ api.mail.send(payload) - メール送信
    │
    └─ InviteModel.edit({status: 'sent'}) - ステータス更新
```

### データフロー図

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

Invites.add()        ───▶ InvitesService ───▶ 招待メール送信
  │                           │
  ├─ invites[0].email        ├─ 既存招待確認・削除
  ├─ options                 ├─ 新規招待作成
  └─ user (招待者)           ├─ トークンBase64エンコード
                             ├─ テンプレート選択（名前有無）
                             ├─ メールコンテンツ生成
                             ├─ メール送信
                             └─ ステータス更新

[出力]
  ├─ 招待メール（招待リンク付き）
  └─ 更新された招待オブジェクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| invites.js | `ghost/core/core/server/services/invites/invites.js` | ソース | 招待サービスメイン |
| invite-user | メールテンプレート（mail serviceで管理） | テンプレート | 招待者名ありの場合 |
| invite-user-by-api-key | メールテンプレート（mail serviceで管理） | テンプレート | API経由の場合 |
