# 通知設計書 14-アカウント解約メール

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるアカウント解約メール通知の設計仕様を定義する。アカウントが解約された際に、解約を実行したユーザーに対して解約完了の通知と今後の流れを説明するメールを送信する機能について記載する。

### 本通知の処理概要

本通知は、ユーザーがアカウントの解約を実行した際に自動送信されるメール通知である。解約処理が完了したことを確認し、データ削除までの猶予期間やサポートへの連絡方法を案内する。

**業務上の目的・背景**：アカウント解約は重要な操作であり、ユーザーに対して確実に完了を通知する必要がある。また、誤って解約した場合の救済手段（30日間の猶予期間内であればサポートへの連絡で復元可能）を案内することで、ユーザー体験を向上させる。課金サービスの場合は請求停止の確認も重要な情報となる。

**通知の送信タイミング**：Account#cancelメソッド内でCancellationレコードが作成された直後に送信される。具体的には、Cancellableモジュールのcancelメソッド内で、create_cancellation!の後にAccountMailer.cancellation(cancellation).deliver_laterが実行される。

**通知の受信者**：解約を実行したユーザー（cancellation.initiated_by）のメールアドレスに送信される。アカウントの全ユーザーではなく、解約操作を行った本人のみに送信される。これにより、解約の責任者が明確になる。

**通知内容の概要**：メール本文には、解約されたアカウント名、今後の流れ（アクセス不可、課金停止、データ削除までの期間）、および解約取り消しの方法（サポートへの連絡）が含まれる。

**期待されるアクション**：ユーザーは解約完了を確認する。もし誤って解約した場合は、30日以内にサポート（support@fizzy.do）へ連絡してアカウントの復元を依頼する。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Active Jobのデフォルト設定に従う |

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

解約を実行したユーザー（cancellation.initiated_by）のIdentityに紐づくメールアドレスに送信される。送信先は`@user.identity.email_address`で決定される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | 環境変数MAILER_FROM_ADDRESSまたはデフォルト値 |
| 送信元名称 | Fizzy |
| 件名 | Your Fizzy account was cancelled |
| 形式 | HTML/テキスト（マルチパート） |

### 本文テンプレート

#### HTML版

```html
<p>Your Fizzy account <strong>{{account_name}}</strong> was cancelled.</p>

<h2>What happens now?</h2>

<ul>
  <li>No one can access the account anymore</li>
  <!-- 課金サービスの場合 -->
  <li>We won't charge you anymore</li>
  <li>Everything in the account will be deleted in {{incineration_period}}</li>
</ul>

<i>Changed your mind?</i>
<p>If you want to cancel this deletion and restore your account, send us an email to <a href="mailto:support@fizzy.do">support@fizzy.do</a> as soon as possible.</p>
```

#### テキスト版

```
Your Fizzy account "{{account_name}}" was cancelled.

WHAT HAPPENS NOW?

- No one can access the account anymore
<!-- 課金サービスの場合 -->
- We won't charge you anymore
- Everything in the account will be deleted in {{incineration_period}}

CHANGED YOUR MIND?

If you want to cancel this deletion and restore your account, send us an email to support@fizzy.do as soon as possible.
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @account | 解約されたアカウント | cancellation.account | Yes |
| @account.name | アカウント名 | accounts.name | Yes |
| @user | 解約実行ユーザー | cancellation.initiated_by | Yes |
| @account.subscription | 課金情報（存在する場合） | 関連テーブル | No |
| INCINERATION_GRACE_PERIOD | データ削除までの猶予期間 | Account::Incineratable定数（30日） | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | アカウント解約実行 | cancellable?かつactive?がtrue | Account#cancel内でCancellation作成後に送信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| cancellable?がfalse | サインアップ受付停止中はキャンセル不可 |
| active?がfalse | 既に解約済みまたはインポート中のアカウント |
| 既にcancellationが存在 | 二重解約の防止 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーがアカウント解約を実行] --> B{cancellable?}
    B -->|No| C[解約不可]
    B -->|Yes| D{active?}
    D -->|No| C
    D -->|Yes| E[with_lock トランザクション開始]
    E --> F[run_callbacks :cancel]
    F --> G[create_cancellation!]
    G --> H[AccountMailer.cancellation.deliver_later]
    H --> I[メール送信キューに追加]
    I --> J[メール送信実行]
    J --> K{送信結果}
    K -->|成功| L[送信完了]
    K -->|失敗| M[リトライ/エラー処理]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| account_cancellations | 解約情報取得 | 解約日時、実行者取得 |
| accounts | アカウント情報 | 名前取得 |
| users | 解約実行ユーザー | initiated_by |
| identities | メールアドレス取得 | 送信先決定 |

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

#### account_cancellations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | 解約識別 | 引数で渡される |
| account_id | アカウント特定 | belongs_to関連 |
| initiated_by_id | 解約実行者特定 | belongs_to関連 |
| created_at | 解約日時 | データ削除期限計算に使用 |

#### accounts

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | アカウント識別 | cancellation.account |
| name | アカウント名表示 | メール本文で使用 |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別 | cancellation.initiated_by |
| identity_id | Identity取得 | belongs_to関連 |

#### identities

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email_address | 送信先メールアドレス | user.identity |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| account_cancellations | INSERT | 解約レコード作成 |

#### account_cancellations作成

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | account_id | 解約対象アカウントID | |
| INSERT | initiated_by_id | 解約実行ユーザーID | |
| INSERT | created_at | Time.current | |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPエラー等 | Active Jobによるリトライ |
| 宛先不正 | メールアドレス形式不正 | 送信エラーとしてログ記録 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Active Jobデフォルト |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なSMTPエラー |

## 配信設定

### レート制限

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

### 配信時間帯

特に制限なし（解約実行時に即座に送信）

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

- 解約メールは解約を実行したユーザーのみに送信され、他のアカウントメンバーには送信されない
- アカウントデータは30日間の猶予期間（INCINERATION_GRACE_PERIOD）後に完全削除される
- 猶予期間内であれば、サポートへの連絡でアカウントを復元可能（reactivateメソッド）
- with_lockによるトランザクション制御で二重解約を防止
- メールアドレスはIdentityテーブルで管理され、認証済みのアドレスのみ使用

## 備考

- 課金サービス（subscription）が存在する場合、請求停止のメッセージが追加表示される
- 解約後もreactivateメソッドでアカウントを復元可能（30日以内）
- 30日経過後はdue_for_incinerationスコープで抽出され、incinerateメソッドでアカウントが完全削除される
- Account.accepting_signups?がfalseの場合、解約機能自体が無効化される

---

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

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

### 推奨読解順序

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

まず、解約機能で使用されるモデルの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | account/cancellation.rb | `app/models/account/cancellation.rb` | 解約レコードモデル。account、initiated_byの関連を確認 |
| 1-2 | account/cancellable.rb | `app/models/account/cancellable.rb` | 解約処理のconcern。cancel/reactivateメソッド、コールバックを確認 |
| 1-3 | account/incineratable.rb | `app/models/account/incineratable.rb` | データ削除のconcern。INCINERATION_GRACE_PERIOD（30日）を確認 |

**読解のコツ**: Accountモデルはconcernを多用している。Cancellable→Incineratableの順で解約→削除の流れを理解する。

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

解約処理の起点となるcancelメソッドを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | account/cancellable.rb | `app/models/account/cancellable.rb` | cancelメソッド（行11-20）がエントリーポイント |

**主要処理フロー**:
1. **行13**: `cancellable? && active?` - 解約可能条件チェック
2. **行14-16**: `run_callbacks :cancel` - 解約コールバック実行
3. **行15**: `create_cancellation!` - 解約レコード作成
4. **行18**: `AccountMailer.cancellation.deliver_later` - メール送信

#### Step 3: メーラーを理解する

メール送信処理の実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | account_mailer.rb | `app/mailers/account_mailer.rb` | メーラークラス。cancellationメソッドを確認 |

**主要処理フロー**:
- **行2-9（account_mailer.rb）**: cancellationメソッドで@account、@userを設定

#### Step 4: ビューテンプレートを理解する

メール本文のテンプレートと条件分岐を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cancellation.html.erb | `app/views/mailers/account_mailer/cancellation.html.erb` | HTML版テンプレート。subscription分岐を確認 |
| 4-2 | cancellation.text.erb | `app/views/mailers/account_mailer/cancellation.text.erb` | テキスト版テンプレート |

**主要処理フロー**:
- **行7-9（cancellation.html.erb）**: `@account.try(:subscription)` による課金メッセージ分岐
- **行10**: `INCINERATION_GRACE_PERIOD.from_now` でデータ削除期限計算

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

```
ユーザー操作（アカウント解約）
    │
    └─ Account#cancel(initiated_by: user)
           │
           ├─ with_lock (トランザクション)
           │      │
           │      ├─ cancellable? && active? チェック
           │      │
           │      ├─ run_callbacks :cancel
           │      │      └─ create_cancellation!(initiated_by: user)
           │      │
           │      └─ AccountMailer.cancellation(cancellation).deliver_later
           │             └─ [非同期] メール送信
           │
           └─ [30日後] due_for_incineration スコープ
                  └─ incinerate
                         └─ account.destroy
```

### データフロー図

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

ユーザー操作 ───▶ Account#cancel
                       │
                       ├─▶ 条件チェック
                       │      ├─ cancellable?
                       │      └─ active?
                       │
                       ├─▶ Cancellation作成
                       │
                       ▼
               AccountMailer.cancellation
                       │
                       ├─▶ @account (アカウント名)
                       ├─▶ @user (送信先)
                       ├─▶ subscription? (課金メッセージ)
                       └─▶ INCINERATION_GRACE_PERIOD (削除期限)
                               │
                               ▼
                       解約メール ───▶ ユーザーのメールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| account.rb | `app/models/account.rb` | ソース | アカウントモデル |
| account/cancellation.rb | `app/models/account/cancellation.rb` | ソース | 解約レコードモデル |
| account/cancellable.rb | `app/models/account/cancellable.rb` | ソース | 解約処理concern |
| account/incineratable.rb | `app/models/account/incineratable.rb` | ソース | データ削除concern |
| account_mailer.rb | `app/mailers/account_mailer.rb` | ソース | アカウントメーラー |
| application_mailer.rb | `app/mailers/application_mailer.rb` | ソース | 基底メーラー |
| cancellation.html.erb | `app/views/mailers/account_mailer/cancellation.html.erb` | テンプレート | HTML版メール本文 |
| cancellation.text.erb | `app/views/mailers/account_mailer/cancellation.text.erb` | テンプレート | テキスト版メール本文 |
| account_mailer_test.rb | `test/mailers/account_mailer_test.rb` | テスト | メーラーテスト |
| cancellable_test.rb | `test/models/account/cancellable_test.rb` | テスト | 解約処理テスト |
