# 通知設計書 1-組織招待メール

## 概要

本ドキュメントは、Liamアプリケーションにおける組織招待メール通知の設計仕様を定義する。組織管理者がメンバーを招待する際に送信されるHTMLメール通知について、送信仕様、テンプレート、処理フローを詳細に記載する。

### 本通知の処理概要

組織招待メールは、組織管理者が新しいメンバーを組織に招待する際に、招待対象者のメールアドレスへ自動送信されるHTMLメール通知である。

**業務上の目的・背景**：組織へのメンバー招待は、チームコラボレーションの基盤となる重要な機能である。招待メールを通じて、招待対象者が組織への参加手続きをスムーズに行えるようにすることで、オンボーディング体験を向上させ、組織の生産性向上に貢献する。また、招待リンクを含むメールにより、セキュアかつ確実に招待プロセスを完了できる。

**通知の送信タイミング**：組織設定画面のメンバー管理ページにて、管理者が「Invite Members」モーダルから招待対象者のメールアドレスを入力し、「Invite」ボタンを押下した際に送信される。具体的には、`inviteMember`サーバーアクションが正常に完了し、招待トークンが発行された直後に`sendInvitationEmail`関数が呼び出されてメールが送信される。

**通知の受信者**：招待対象者として入力されたメールアドレスの所有者。招待対象のメールアドレスは、フォーム入力時にバリデーションされ、有効なメールアドレス形式であることが確認される。

**通知内容の概要**：招待メールには、招待先組織の名称と、招待を受諾するためのリンク（招待トークン付きURL）が含まれる。HTMLメール形式で送信され、視認性の高いリンクボタンが配置される。

**期待されるアクション**：受信者は、メール本文に記載された「Accept Invitation」リンクをクリックして招待を受諾し、組織への参加手続きを完了することが期待される。招待を受ける意思がない場合は、メールを無視しても問題ない旨も記載されている。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（サーバーアクション内で同期的に呼び出し） |
| 優先度 | 中 |
| リトライ | なし（Resend API側でのリトライに依存） |

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

招待フォームに入力されたメールアドレスが送信先となる。入力されたメールアドレスは以下の処理を経る：
1. フロントエンドでの形式バリデーション（正規表現による検証）
2. サーバーサイドでのvalibotスキーマによるバリデーション
3. 小文字への正規化（transform処理）

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `${RESEND_EMAIL_FROM_ADDRESS}` または `liam@resend.dev`（デフォルト） |
| 送信元名称 | Liam |
| 件名 | `Invitation to join ${organizationName} on Liam` |
| 形式 | HTML |

### 本文テンプレート

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Organization Invitation</title>
  </head>
  <body>
    <h1>You've been invited to join ${organizationName}</h1>
    <p>You have been invited to join ${organizationName} on Liam.</p>
    <p>Click the link below to accept the invitation:</p>
    <a href="${invitationLink}">Accept Invitation</a>
    <p>If you did not expect this invitation, you can safely ignore this email.</p>
  </body>
</html>
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| organizationName | 招待先組織の名称 | organizations.name | Yes |
| invitationLink | 招待受諾用のURL | 環境変数 + invitations.invite_token | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | InviteMemberModalでの招待実行 | 招待トークン発行成功かつRESEND_API_KEY設定あり | メンバー招待モーダルから招待ボタン押下時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| RESEND_API_KEY未設定 | 環境変数`RESEND_API_KEY`が設定されていない場合、メール送信をスキップして成功扱いとする |
| 組織情報取得失敗 | 組織IDに対応する組織が存在しない、または取得に失敗した場合 |
| メール送信API失敗 | Resend APIからエラーレスポンスが返された場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[招待フォーム送信] --> B[フォームバリデーション]
    B -->|バリデーションOK| C[inviteMember Server Action呼び出し]
    B -->|バリデーションNG| D[エラー表示]
    C --> E[RPC: invite_organization_member実行]
    E -->|成功| F[招待トークン取得]
    E -->|失敗| G[エラーレスポンス返却]
    F --> H{RESEND_API_KEY設定確認}
    H -->|未設定| I[成功扱いで終了]
    H -->|設定あり| J[組織情報取得]
    J -->|成功| K[招待リンク生成]
    J -->|失敗| L[エラーレスポンス返却]
    K --> M[Resend API呼び出し]
    M -->|成功| N[成功レスポンス返却]
    M -->|失敗| O[エラーレスポンス返却]
    N --> P[成功トースト表示]
    G --> Q[エラートースト表示]
    L --> Q
    O --> Q
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| organizations | 組織名の取得 | メール件名・本文に使用 |

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

#### organizations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | メール件名・本文への組織名埋め込み | `WHERE id = ${organizationId}` |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| invitations | INSERT（RPC経由） | 招待レコードの作成（invite_organization_member RPC内で実行） |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| - | - | - | 専用の送信ログテーブルは存在しない。Resend API側でログ管理。 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 組織情報取得失敗 | 指定された組織IDに対応する組織が存在しない | エラーレスポンスを返却し、フロントエンドでエラートースト表示 |
| メール送信失敗 | Resend APIからエラーレスポンス | エラーレスポンスを返却し、フロントエンドでエラートースト表示 |
| バリデーション失敗 | メールアドレス形式不正 | フロントエンドでインラインエラー表示 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（アプリケーション側では実装なし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Resend APIの制限に依存 |
| 1日あたり上限 | Resend APIの制限に依存 |

### 配信時間帯

特に制限なし。ユーザー操作に応じて即座に送信される。

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

- 招待トークンはUUIDv4形式で生成され、推測困難
- 招待リンクはHTTPS経由でのみアクセス可能
- 招待トークンには有効期限が設定される（データベース側で管理）
- メールアドレスは小文字に正規化して一意性を確保

## 備考

- Resend APIを使用。環境変数`RESEND_API_KEY`が設定されている場合のみ実際に送信される
- 開発環境では`liam@resend.dev`からの送信となる
- 本番環境では`NEXT_PUBLIC_BASE_URL`を使用、プレビュー環境では`VERCEL_BRANCH_URL`を使用して招待リンクを構築

---

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

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

### 推奨読解順序

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

まず、招待フォームのバリデーションスキーマと、招待処理の結果スキーマを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | inviteMember.ts | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/actions/inviteMember.ts` | inviteFormSchema（9-16行目）でフォームデータのバリデーション、invitationResultSchema（18-28行目）でRPCレスポンスの型定義を理解する |

**読解のコツ**: valibotスキーマを使用しており、v.pipe()でバリデーションチェーンを構築している。transform()でメールアドレスの小文字変換を行っている点に注目。

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

処理の起点となるUIコンポーネントを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | InviteMemberModal.tsx | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/InviteMemberModal.tsx` | handleSubmit関数（56-110行目）でフォーム送信処理の流れを理解する |

**主要処理フロー**:
1. **56-60行目**: フォーム送信イベントハンドラ開始、状態リセット
2. **63-72行目**: クライアントサイドのメールアドレスバリデーション
3. **74-81行目**: startTransitionでFormData作成とサーバーアクション呼び出し
4. **83-94行目**: エラー時のハンドリングとエラートースト表示
5. **96-108行目**: 成功時の状態更新と成功トースト表示

#### Step 3: サーバーアクションを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | inviteMember.ts | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/actions/inviteMember.ts` | サーバーサイドでのバリデーションとRPC呼び出し（34-96行目）を理解する |

**主要処理フロー**:
- **36-47行目**: FormDataのパースとvalibotによるバリデーション
- **52-56行目**: Supabase RPCでinvite_organization_member呼び出し
- **66-77行目**: レスポンスのバリデーションと型絞り込み
- **79-91行目**: sendInvitationEmail呼び出しとエラーハンドリング

#### Step 4: メール送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | sendInvitationEmail.ts | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/actions/sendInvitationEmail.ts` | メール送信の実装詳細（38-102行目）を理解する |

**主要処理フロー**:
- **43-45行目**: RESEND_API_KEY未設定時の早期リターン
- **50-62行目**: 組織情報の取得
- **64-75行目**: 環境に応じた招待リンクのベースURL決定
- **77-78行目**: 招待リンクの構築
- **80-91行目**: Resend APIを使用したメール送信

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

```
InviteMemberModal.tsx
    │
    └─ handleSubmit()
           │
           └─ inviteMember() [Server Action]
                  │
                  ├─ v.safeParse() [Validation]
                  │
                  ├─ supabase.rpc('invite_organization_member')
                  │
                  └─ sendInvitationEmail()
                         │
                         ├─ supabase.from('organizations').select()
                         │
                         └─ resend.emails.send()
```

### データフロー図

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

email (フォーム入力)   ───▶ inviteMember Server Action  ───▶ 招待メール送信
organizationId         ───▶   │                              │
                              │                              ▼
                              ├─▶ RPC呼び出し             トースト通知
                              │     └─▶ invitations INSERT    (成功/失敗)
                              │
                              └─▶ sendInvitationEmail
                                    ├─▶ organizations SELECT
                                    └─▶ Resend API
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| InviteMemberModal.tsx | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/InviteMemberModal.tsx` | ソース | 招待モーダルUIコンポーネント |
| inviteMember.ts | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/actions/inviteMember.ts` | ソース | 招待処理サーバーアクション |
| sendInvitationEmail.ts | `frontend/apps/app/components/OrganizationMembersPage/components/InviteMemberModal/actions/sendInvitationEmail.ts` | ソース | メール送信処理 |
| Toast.tsx | `frontend/packages/ui/src/components/Toast/Toast.tsx` | ソース | トースト通知UIコンポーネント |
| useToast.tsx | `frontend/packages/ui/src/components/Toast/useToast.tsx` | ソース | トーストフック |
