# 通知設計書 21-メール通知

## 概要

本ドキュメントは、InvenTreeシステムにおけるメール通知機能の設計について記述します。InvenTreeEmailNotificationsプラグインは、システム内で発生する各種イベントに対する通知をユーザーのメールアドレスに送信するコア機能を提供します。

### 本通知の処理概要

メール通知プラグインは、InvenTreeの通知フレームワークを利用して、各種イベント発生時にユーザーへメールを送信します。

**業務上の目的・背景**：在庫管理システムにおいて、購買発注、販売発注、製造指示、在庫アラートなど重要なイベントが発生した際に、関係者がタイムリーに情報を受け取る必要があります。メール通知は最も普及した通知手段であり、オフィス外や非同期での情報伝達を可能にします。これにより、業務の遅延を防ぎ、迅速な意思決定をサポートします。

**通知の送信タイミング**：各種イベント（新規発注作成、発注キャンセル、期限超過、在庫不足など）が発生した際に、`trigger_notification`関数が呼び出され、登録されたNotificationMixinを持つプラグインに対して通知送信が要求されます。メール通知プラグインは、テンプレートが指定されたコンテキストを受け取った場合にメールを送信します。

**通知の受信者**：各イベントの対象者（発注担当者、グループメンバーなど）がターゲットとして指定されます。メール通知プラグインは、各ユーザーの`NOTIFY_BY_EMAIL`設定を確認し、有効なユーザーのみにメールを送信します。ユーザーのメールアドレスは、Userモデルのemailフィールドまたはallauth EmailAddressモデルから取得されます。

**通知内容の概要**：通知の種類に応じたHTMLテンプレートを使用してメール本文が生成されます。件名にはINVENTREE_INSTANCE設定値がプレフィックスとして付与されます。

**期待されるアクション**：受信者はメールの内容を確認し、必要に応じてInvenTreeシステムにアクセスして適切な対応（承認、在庫補充、出荷準備など）を行います。

## 通知種別

メール（Email）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（offload_task経由） |
| 優先度 | 通常（Priority.NORMAL） |
| リトライ | 設定なし（単発送信） |

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

1. `trigger_notification`で指定されたターゲット（User、Group、Owner）からユーザーリストを生成
2. 各ユーザーの`NOTIFY_BY_EMAIL`設定を確認（デフォルト: True）
3. 設定が有効なユーザーのメールアドレスを取得（User.email または EmailAddress）
4. 有効なメールアドレスを持つユーザーにのみ送信

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `DEFAULT_FROM_EMAIL`設定値（`INVENTREE_EMAIL_SENDER`または`INVENTREE_EMAIL_USERNAME`） |
| 送信元名称 | 設定なし（メールアドレスのみ） |
| 件名 | `[{INVENTREE_INSTANCE}] {context.template.subject}` |
| 形式 | HTML（代替テキストなし） |

### 本文テンプレート

テンプレートはイベントの種類に応じて異なります。ベーステンプレートは`templates/email/email.html`で定義され、以下の構造を持ちます：

```html
<table style='border-collapse: collapse; width: 85%; margin-left: 10%; ...'>
    {% block header %}
    <tr style='background: #eef3f7; ...'>
        <th>
            {% block title %}<!-- タイトル -->{% endblock %}
            {% block subtitle %}<!-- サブタイトル -->{% endblock %}
        </th>
    </tr>
    {% endblock %}

    {% block body %}
    <tr>
        {% block body_row %}<!-- 本文 -->{% endblock %}
    </tr>
    {% endblock %}

    {% block footer %}
    <tr style='background: #eef3f7; ...'>
        <td>
            <p>InvenTree Version - InvenTree Documentation</p>
        </td>
    </tr>
    {% endblock %}
</table>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 現行実装では添付ファイル機能なし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| template.html | HTMLテンプレートパス | context['template']['html'] | Yes |
| template.subject | メール件名 | context['template']['subject'] | No |
| name | 通知名 | context['name'] | Yes |
| message | 通知メッセージ | context['message'] | Yes |
| link | 関連オブジェクトへのリンク | context['link'] | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作/API | 発注作成・キャンセル等 | trigger_notification呼び出し時 | 各モデルの操作時に発火 |
| スケジュール | 期限超過チェック | 日次バッチ処理 | notify_overdue_*関数 |
| システムイベント | 在庫アラート | 閾値到達時 | notify_low_stock等 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| NOTIFY_BY_EMAIL=False | ユーザーがメール通知を無効にしている場合 |
| templateなし | context['template']が存在しない場合 |
| メールアドレスなし | ユーザーのメールアドレスが設定されていない場合 |
| メール設定未完了 | EMAIL_HOST、DEFAULT_FROM_EMAILが未設定の場合 |
| 最近送信済み | 同一カテゴリ・オブジェクトに24時間以内に送信済みの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[イベント発生] --> B[trigger_notification呼び出し]
    B --> C[ターゲットユーザー解決]
    C --> D[NotificationEntryで重複チェック]
    D -->|24時間以内に送信済み| E[送信スキップ]
    D -->|送信可能| F[プラグインループ]
    F --> G[InvenTreeEmailNotifications.send_notification]
    G --> H{テンプレートあり?}
    H -->|No| I[return False]
    H -->|Yes| J[ユーザーループ]
    J --> K{NOTIFY_BY_EMAIL=True?}
    K -->|No| L[スキップ]
    K -->|Yes| M[メールアドレス取得]
    M --> N{アドレスあり?}
    N -->|No| L
    N -->|Yes| O[受信者リストに追加]
    L --> P{全ユーザー処理完了?}
    O --> P
    P -->|No| J
    P -->|Yes| Q{受信者あり?}
    Q -->|No| I
    Q -->|Yes| R[HTMLテンプレートレンダリング]
    R --> S[send_email呼び出し]
    S --> T[offload_taskでissue_mail]
    T --> U[EmailMultiAlternatives生成]
    U --> V[メール送信]
    V --> W[NotificationEntry記録]
    W --> X[終了]
    E --> X
    I --> X
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| auth_user | ユーザー情報・メールアドレス取得 | Django標準 |
| account_emailaddress | 代替メールアドレス取得 | allauth |
| common_notificationentry | 重複送信チェック | 24時間以内の送信履歴 |
| plugin_pluginsetting | NOTIFY_BY_EMAIL設定取得 | プラグイン設定 |

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

#### auth_user

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email | メールアドレス | ターゲットユーザー |
| is_active | アクティブチェック | True のみ |

#### account_emailaddress

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email | 代替メールアドレス | user.email が空の場合 |
| primary | 優先順位 | ORDER BY -primary, -verified |
| verified | 検証済みフラグ | ORDER BY -primary, -verified |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| common_notificationentry | INSERT/UPDATE | 送信履歴の記録 |

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

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT/UPDATE | key | カテゴリ名 | 通知カテゴリ |
| INSERT/UPDATE | uid | オブジェクトID | 対象オブジェクトのPK |
| INSERT/UPDATE | updated | 現在時刻 | 自動更新 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPサーバー接続エラー | ログ出力、例外をキャッチして継続 |
| テンプレートエラー | テンプレートファイルが存在しない | render_to_string例外、ログ出力 |
| 宛先不正 | 無効なメールアドレス形式 | SMTPエラーとして処理 |
| 設定不備 | EMAIL_HOST未設定 | is_email_configured()でFalse返却 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- SMTPパスワード（EMAIL_HOST_PASSWORD）は設定ファイルで管理
- メール内容にはユーザーがアクセス権を持つ情報のみ含まれる（check_user_permissionで検証）
- HTMLメールテンプレートはサーバー側で管理され、XSS攻撃のリスクは低い
- TLS/SSL対応（EMAIL_USE_TLS、EMAIL_USE_SSL設定）

## 備考

- プラグインはINVENTREE必須プラグイン（MANDATORY_PLUGINS）に含まれる
- プラグインスラッグ: `inventree-email-notification`
- バージョン: 1.0.0

---

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

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

### 推奨読解順序

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

まず、通知で受け渡されるデータ構造とユーザー設定を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.py | `src/backend/InvenTree/common/notifications.py` | NotificationBody dataclass（26-47行目）で通知の基本構造を理解 |
| 1-2 | models.py | `src/backend/InvenTree/common/models.py` | NotificationEntry（1616-1651行目）で重複チェックの仕組みを理解 |

**読解のコツ**: `NotificationBody`は`name`, `slug`, `message`, `template`の4属性を持つdataclassで、f-string形式で動的に値が埋め込まれます。

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

処理の起点となる`trigger_notification`関数を特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notifications.py | `src/backend/InvenTree/common/notifications.py` | trigger_notification関数（87-218行目）が全ての通知の起点 |

**主要処理フロー**:
1. **87-98行目**: 関数シグネチャとデータインポート中チェック
2. **100-107行目**: 引数の取得（targets, context, delivery_methods等）
3. **126-137行目**: NotificationEntryによる重複送信チェック（24時間以内）
4. **144-175行目**: ターゲット（User, Group, Owner）からユーザーリストへの変換
5. **177-185行目**: アクティブユーザーとパーミッションのフィルタリング
6. **191-214行目**: 登録プラグインへの通知ディスパッチ
7. **217-218行目**: NotificationEntryへの記録

#### Step 3: メール通知プラグインを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | core_notifications.py | `src/backend/InvenTree/plugin/builtin/integration/core_notifications.py` | InvenTreeEmailNotifications（63-116行目）がメール送信の実装 |

**主要処理フロー**:
- **63-80行目**: クラス定義とUSER_SETTINGS（NOTIFY_BY_EMAIL）
- **82-116行目**: send_notification実装
  - **87-88行目**: テンプレート存在チェック
  - **90行目**: HTMLテンプレートのレンダリング
  - **93-97行目**: インスタンス名を件名にプレフィックス
  - **101-107行目**: NOTIFY_BY_EMAIL設定確認とメールアドレス収集
  - **109-113行目**: send_email呼び出し

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | helpers_email.py | `src/backend/InvenTree/InvenTree/helpers_email.py` | send_email（63-113行目）とget_email_for_user（116-130行目） |

**主要処理フロー**:
- **63-73行目**: send_email関数シグネチャとrecipients正規化
- **76-85行目**: データインポート中・メール未設定チェック
- **88-99行目**: from_emailのデフォルト設定
- **101-113行目**: offload_taskでissue_mailをバックグラウンド実行
- **116-130行目**: ユーザーのメールアドレス取得（User.email優先、次にEmailAddress）

#### Step 5: 実際のメール送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | models.py | `src/backend/InvenTree/common/models.py` | issue_mail関数（3171-3208行目）が実際のSMTP送信 |

**主要処理フロー**:
- **3185行目**: SMTPコネクション取得
- **3187-3188行目**: EmailMultiAlternatives生成
- **3190-3191行目**: HTML代替コンテンツ添付
- **3194-3206行目**: ヘッダー設定（Message-ID、Priority等）
- **3208行目**: message.send()で送信

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

```
trigger_notification() [common/notifications.py:87]
    │
    ├─ NotificationEntry.check_recent() [common/models.py:1637]
    │      └─ 24時間以内の送信履歴チェック
    │
    ├─ ターゲット解決処理
    │      ├─ User → 直接追加
    │      ├─ Group → Group.usersを展開
    │      └─ Owner → get_related_owners()で展開
    │
    ├─ registry.with_mixin(NOTIFICATION) でプラグイン取得
    │
    └─ plugin.send_notification() [各プラグイン]
           │
           └─ InvenTreeEmailNotifications.send_notification()
                  [plugin/builtin/integration/core_notifications.py:82]
                  │
                  ├─ render_to_string() でHTMLレンダリング
                  │
                  ├─ get_user_setting('NOTIFY_BY_EMAIL')
                  │
                  ├─ get_email_for_user() [InvenTree/helpers_email.py:116]
                  │      ├─ user.email チェック
                  │      └─ EmailAddress.objects.filter() フォールバック
                  │
                  └─ send_email() [InvenTree/helpers_email.py:63]
                         │
                         └─ offload_task(issue_mail, ...)
                                │
                                └─ issue_mail() [common/models.py:3171]
                                       │
                                       ├─ EmailMultiAlternatives生成
                                       └─ message.send()
```

### データフロー図

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

イベント発生        ───▶ trigger_notification()      ───▶ NotificationEntry記録
(model操作等)              │
                           │
context:                   ▼
  - name             プラグインディスパッチ
  - message                │
  - template               │
  - link                   ▼
                    InvenTreeEmailNotifications
                    .send_notification()
                           │
targets:                   │
  - User             ┌─────┴─────┐
  - Group            │           │
  - Owner            ▼           ▼
                 NOTIFY_BY     template
                 EMAIL確認     レンダリング
                     │           │
                     ▼           ▼
               recipients    html_message
                     │           │
                     └─────┬─────┘
                           │
                           ▼
                      send_email()
                           │
                           ▼
                    offload_task()     ───▶ メール送信（SMTP）
                           │
                           ▼
                      issue_mail()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notifications.py | `src/backend/InvenTree/common/notifications.py` | ソース | 通知フレームワークのコア |
| core_notifications.py | `src/backend/InvenTree/plugin/builtin/integration/core_notifications.py` | ソース | メール通知プラグイン実装 |
| helpers_email.py | `src/backend/InvenTree/InvenTree/helpers_email.py` | ソース | メール送信ヘルパー関数 |
| models.py | `src/backend/InvenTree/common/models.py` | ソース | NotificationEntry, issue_mail |
| NotificationMixin.py | `src/backend/InvenTree/plugin/base/integration/NotificationMixin.py` | ソース | 通知プラグインの基底クラス |
| email.html | `src/backend/InvenTree/templates/email/email.html` | テンプレート | メールのベーステンプレート |
| settings.py | `src/backend/InvenTree/InvenTree/settings.py` | 設定 | EMAIL_HOST等のメール設定 |
| test_core_notifications.py | `src/backend/InvenTree/plugin/builtin/integration/test_core_notifications.py` | テスト | メール通知のユニットテスト |
