最終更新日 → 2026-02-25 プロジェクト構造 → mail/ と settings/password/ ページを追加 データモデル概要 → MailSender, MailEmail, MailNotificationToken を追加 実装状況 → メールフィルタリング機能を本番稼働済みに更新、パスワード変更機能を追加 マスタードキュメントリンク → document/11_マスタードキュメント_メール通知関連編.md を追加 トラブルシューティング → 本番デプロイコマンド(--env-file .env.production 必須)を冒頭に追加 更新履歴 → 今回の変更を記録
600 lines
22 KiB
Markdown
600 lines
22 KiB
Markdown
# マスタードキュメント - メール通知関連編
|
||
|
||
> **最終更新**: 2026-02-25
|
||
> **対象バージョン**: Phase 1 完了時点(本番稼働中)
|
||
> **目的**: このドキュメントだけでメール通知機能の全容を把握できること
|
||
|
||
---
|
||
|
||
## 目次
|
||
|
||
1. [システム概要・全体構成](#1-システム概要全体構成)
|
||
2. [データモデル](#2-データモデル)
|
||
3. [API仕様(バックエンド)](#3-api仕様バックエンド)
|
||
4. [Windmill フロー仕様](#4-windmill-フロー仕様)
|
||
5. [画面仕様(フロントエンド)](#5-画面仕様フロントエンド)
|
||
6. [本番環境の設定値](#6-本番環境の設定値)
|
||
7. [設計判断と制約](#7-設計判断と制約)
|
||
8. [運用手順](#8-運用手順)
|
||
9. [ソースファイル索引](#9-ソースファイル索引)
|
||
|
||
---
|
||
|
||
## 1. システム概要・全体構成
|
||
|
||
### 機能の目的
|
||
|
||
複数のメールアカウント(Gmail × 2 + Xserver × 6 = 計8アカウント)に届く大量のメールを自動でフィルタリングし、農家にとって重要なメールだけを LINE で通知する。
|
||
|
||
### システム構成図
|
||
|
||
```
|
||
[メールサーバー群] [Windmill] [KeinaSystem]
|
||
Gmail (2口) ─IMAP─▶ mail_filter フロー ──API──▶ Django バックエンド
|
||
Xserver (6口) ↓ (DB記録・ルール参照)
|
||
[Gemini API] ↓
|
||
LLM判定 フロントエンド
|
||
↓ ・メール履歴画面
|
||
LINE Messaging API ・ルール管理画面
|
||
(重要と判定した場合のみ通知) ・フィードバックページ
|
||
↑
|
||
LINE通知文にフィードバックURL ────────┘
|
||
を含め、ユーザーがタップして
|
||
フィードバックを送信
|
||
```
|
||
|
||
### 処理フロー(1メールあたり)
|
||
|
||
```
|
||
1. IMAP 接続 → 前回処理済み UID 以降の新着メールを取得
|
||
2. 送信者ルール確認(GET /api/mail/sender-rule/)
|
||
├── never_notify → スキップ(記録しない)
|
||
├── always_notify → LLMスキップ、即 LINE 通知
|
||
└── ルールなし → 3へ
|
||
3. 過去フィードバック集計取得(GET /api/mail/sender-context/)
|
||
4. Gemini API で重要度判定(LLM)
|
||
5. KeinaSystem に記録(POST /api/mail/emails/)
|
||
├── not_important → 記録のみ、通知なし
|
||
└── important → フィードバックURLを発行、LINE 通知
|
||
6. 処理済み最終 UID を Windmill Variable に保存
|
||
```
|
||
|
||
### 10分ごとの定期実行
|
||
|
||
Windmill スケジュール `0 */10 * * * *` で自動実行。サーバー上の production Windmill で稼働。
|
||
|
||
---
|
||
|
||
## 2. データモデル
|
||
|
||
### 2.1 MailSender(送信者ルール)
|
||
|
||
**テーブル名**: `mail_mailsender`
|
||
|
||
| フィールド | 型 | 説明 |
|
||
|---|---|---|
|
||
| `id` | BigAutoField | PK |
|
||
| `email` | EmailField (null可) | アドレス指定ルールの場合 |
|
||
| `domain` | CharField(255, null可) | ドメイン指定ルールの場合 |
|
||
| `rule` | CharField(20) | `never_notify` / `always_notify` |
|
||
| `note` | TextField | メモ(任意)|
|
||
| `created_at` | DateTimeField | 作成日時 |
|
||
| `updated_at` | DateTimeField | 更新日時 |
|
||
|
||
**制約**: `email` と `domain` は必ずどちらか一方のみ設定(DB CHECK 制約 `mail_sender_email_or_domain`)
|
||
|
||
**ルール判定の優先順位**: アドレスルールが先、次にドメインルール
|
||
|
||
### 2.2 MailEmail(受信メール記録)
|
||
|
||
**テーブル名**: `mail_mailemail`
|
||
|
||
| フィールド | 型 | 説明 |
|
||
|---|---|---|
|
||
| `id` | BigAutoField | PK |
|
||
| `account` | CharField(20) | `gmail` / `gmail_service` / `xserver` / `hotmail` |
|
||
| `message_id` | CharField(500, unique) | メールの Message-ID ヘッダー(重複防止に使用)|
|
||
| `sender_email` | EmailField | 送信者メールアドレス |
|
||
| `sender_domain` | CharField(255) | 送信者ドメイン |
|
||
| `subject` | CharField(500) | 件名 |
|
||
| `body_preview` | TextField | 本文冒頭(最大500文字)|
|
||
| `received_at` | DateTimeField | 受信日時 |
|
||
| `llm_verdict` | CharField(20) | `important` / `not_important` |
|
||
| `notified_at` | DateTimeField (null可) | LINE 通知日時(通知済みの場合のみ)|
|
||
| `feedback` | CharField(20, null可) | `important` / `not_important` / `never_notify` / `always_notify` |
|
||
| `feedback_at` | DateTimeField (null可) | フィードバック日時 |
|
||
|
||
**ordering**: `-received_at`(新しい順)
|
||
|
||
**重複防止**: `message_id` の unique 制約。同じメールが複数アカウントで受信された場合は 2件目以降を「重複メール、スキップ」として処理(400エラーを無視)。
|
||
|
||
### 2.3 MailNotificationToken(フィードバック用トークン)
|
||
|
||
**テーブル名**: `mail_mailnotificationtoken`
|
||
|
||
| フィールド | 型 | 説明 |
|
||
|---|---|---|
|
||
| `id` | BigAutoField | PK |
|
||
| `email` | OneToOneField → MailEmail | |
|
||
| `token` | UUIDField (unique) | フィードバック URL 用 UUID |
|
||
| `created_at` | DateTimeField | |
|
||
|
||
**用途**: `important` と判定されたメールに対して作成。`/mail/feedback/<token>/` の URL をLINE通知文に含める。有効期限なし。
|
||
|
||
---
|
||
|
||
## 3. API仕様(バックエンド)
|
||
|
||
ベース URL: `https://main.keinafarm.net/api/mail/`
|
||
|
||
### 3.1 認証方式
|
||
|
||
| 認証方式 | 対象エンドポイント | ヘッダー |
|
||
|---|---|---|
|
||
| APIキー認証(Windmill用) | sender-rule, sender-context, POST emails/ | `X-API-Key: <MAIL_API_KEY>` |
|
||
| JWT認証(フロントエンド用) | GET emails/, stats/, senders/, PATCH emails/<pk>/feedback/ | `Authorization: Bearer <token>` |
|
||
| 認証不要 | GET/POST feedback/<token>/ | なし |
|
||
|
||
**MAIL_API_KEY**: `.env.production` の `MAIL_API_KEY` と一致している必要がある。Windmill Variable `u/admin/KEINASYSTEM_API_KEY` に設定。
|
||
|
||
### 3.2 Windmill向けエンドポイント
|
||
|
||
#### GET /api/mail/sender-rule/
|
||
|
||
送信者ルールを確認する。
|
||
|
||
**リクエスト**: クエリパラメータ `email` `domain`
|
||
|
||
**レスポンス例**:
|
||
```json
|
||
{"matched": true, "rule": "never_notify", "match_type": "address"}
|
||
{"matched": false}
|
||
```
|
||
|
||
**判定順序**: アドレス一致 → ドメイン一致 → マッチなし
|
||
|
||
---
|
||
|
||
#### GET /api/mail/sender-context/
|
||
|
||
過去フィードバックの集計を返す(LLMへのコンテキスト用)。
|
||
|
||
**リクエスト**: クエリパラメータ `email` `domain`
|
||
|
||
**レスポンス例**:
|
||
```json
|
||
{
|
||
"total_notified": 8,
|
||
"important": 2,
|
||
"not_important": 5,
|
||
"never_notify": 0,
|
||
"no_feedback": 1
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### POST /api/mail/emails/
|
||
|
||
メールを記録し、`important` の場合はフィードバックURLを発行する。
|
||
|
||
**リクエストボディ**:
|
||
```json
|
||
{
|
||
"account": "gmail",
|
||
"message_id": "<unique-message-id>",
|
||
"sender_email": "sender@example.com",
|
||
"sender_domain": "example.com",
|
||
"subject": "件名",
|
||
"body_preview": "本文冒頭...",
|
||
"received_at": "2026-02-25T15:46:00+09:00",
|
||
"llm_verdict": "important"
|
||
}
|
||
```
|
||
|
||
**レスポンス例**:
|
||
```json
|
||
{"id": 69, "feedback_url": "https://main.keinafarm.net/mail/feedback/<uuid>"}
|
||
```
|
||
`not_important` の場合: `{"id": 68}`(feedback_url なし)
|
||
|
||
**重複処理**: `message_id` が既存の場合 400 を返す。Windmill 側で「重複メール、スキップ」として処理。
|
||
|
||
---
|
||
|
||
### 3.3 フィードバックエンドポイント(認証不要)
|
||
|
||
#### GET /api/mail/feedback/\<token\>/
|
||
|
||
フィードバックページ表示用にメール情報を返す。
|
||
|
||
**レスポンス例**:
|
||
```json
|
||
{
|
||
"id": 69,
|
||
"sender_email": "sender@example.com",
|
||
"sender_domain": "example.com",
|
||
"subject": "件名",
|
||
"body_preview": "本文...",
|
||
"received_at": "2026-02-25T15:46:00+09:00",
|
||
"feedback": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### POST /api/mail/feedback/\<token\>/
|
||
|
||
フィードバックを保存する。
|
||
|
||
**リクエストボディ**:
|
||
```json
|
||
{
|
||
"feedback": "never_notify",
|
||
"scope": "address"
|
||
}
|
||
```
|
||
|
||
`feedback` は `important` / `not_important` / `never_notify` / `always_notify` のいずれか。
|
||
`scope` は `never_notify` / `always_notify` の場合のみ必要(`address` / `domain`)。
|
||
|
||
`never_notify` / `always_notify` + scope の場合、`MailSender` レコードを自動 upsert。
|
||
|
||
---
|
||
|
||
### 3.4 フロントエンド向けエンドポイント(JWT認証)
|
||
|
||
#### GET /api/mail/emails/
|
||
|
||
メール処理履歴を返す(最新100件)。
|
||
|
||
**クエリパラメータ**: `account`(アカウント絞り込み)、`verdict`(LLM判定絞り込み)
|
||
|
||
---
|
||
|
||
#### PATCH /api/mail/emails/\<pk\>/feedback/
|
||
|
||
履歴画面から直接フィードバックを更新する。
|
||
|
||
**リクエストボディ**: `feedback`(必須)、`scope`(`never_notify`/`always_notify` 時のみ)
|
||
|
||
---
|
||
|
||
#### GET /api/mail/stats/
|
||
|
||
ダッシュボード用統計。
|
||
|
||
**レスポンス例**:
|
||
```json
|
||
{
|
||
"today_processed": 12,
|
||
"today_notified": 3,
|
||
"feedback_pending": 1,
|
||
"total_rules": 5
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### GET /api/mail/senders/
|
||
|
||
送信者ルール一覧。
|
||
|
||
#### POST /api/mail/senders/
|
||
|
||
送信者ルール追加。`email` または `domain` のどちらか一方を指定。
|
||
|
||
#### DELETE /api/mail/senders/\<id\>/
|
||
|
||
送信者ルール削除。
|
||
|
||
---
|
||
|
||
## 4. Windmill フロー仕様
|
||
|
||
### 4.1 基本情報
|
||
|
||
| 項目 | 値 |
|
||
|---|---|
|
||
| フローパス | `f/mail/mail_filter` |
|
||
| スクリプト言語 | Python 3 |
|
||
| スケジュール | `0 */10 * * * *`(10分ごと)|
|
||
| スケジュールパス | `f/mail/mail_filter_schedule` |
|
||
| Windmill URL | `https://windmill.keinafarm.net` |
|
||
| ワークスペース | `admins` |
|
||
|
||
### 4.2 処理対象アカウント
|
||
|
||
| 変数名 | メールアドレス | サーバー |
|
||
|---|---|---|
|
||
| `XSERVER1` | `akira@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `XSERVER2` | `service@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `XSERVER3` | `midori@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `XSERVER4` | `kouseiren@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `XSERVER5` | `post@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `XSERVER6` | `sales@keinafarm.com` | `sv579.xserver.jp:993` |
|
||
| `GMAIL` | `akiracraftwork@gmail.com` | `imap.gmail.com:993`、All Mail |
|
||
| `GMAIL2` | `akiranoushi@gmail.com` | `imap.gmail.com:993`、All Mail |
|
||
|
||
Hotmail は定義済みだがコメントアウト(未有効化)。
|
||
|
||
### 4.3 Windmill Variables 一覧
|
||
|
||
本番 Windmill (`windmill.keinafarm.net`、ワークスペース `admins`) に設定。
|
||
|
||
| Variable パス | 内容 | Secret |
|
||
|---|---|---|
|
||
| `u/admin/GMAIL_IMAP_USER` | Gmail ユーザー | ✓ |
|
||
| `u/admin/GMAIL_IMAP_PASSWORD` | Gmail アプリパスワード | ✓ |
|
||
| `u/admin/GMAIL2_IMAP_USER` | Gmail2 ユーザー | ✓ |
|
||
| `u/admin/GMAIL2_IMAP_PASSWORD` | Gmail2 アプリパスワード | ✓ |
|
||
| `u/admin/XSERVER1_IMAP_USER` | `akira@keinafarm.com` | — |
|
||
| `u/admin/XSERVER1_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/XSERVER2_IMAP_USER` | `service@keinafarm.com` | — |
|
||
| `u/admin/XSERVER2_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/XSERVER3_IMAP_USER` | `midori@keinafarm.com` | — |
|
||
| `u/admin/XSERVER3_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/XSERVER4_IMAP_USER` | `kouseiren@keinafarm.com` | — |
|
||
| `u/admin/XSERVER4_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/XSERVER5_IMAP_USER` | `post@keinafarm.com` | — |
|
||
| `u/admin/XSERVER5_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/XSERVER6_IMAP_USER` | `sales@keinafarm.com` | — |
|
||
| `u/admin/XSERVER6_IMAP_PASSWORD` | Xserver IMAP パスワード | ✓ |
|
||
| `u/admin/GEMINI_API_KEY` | Gemini API キー | ✓ |
|
||
| `u/admin/LINE_CHANNEL_ACCESS_TOKEN` | LINE Messaging API トークン | ✓ |
|
||
| `u/admin/LINE_TO` | LINE 通知先ユーザー ID | ✓ |
|
||
| `u/admin/KEINASYSTEM_API_KEY` | KeinaSystem API キー(`.env.production` の `MAIL_API_KEY` と同値)| ✓ |
|
||
| `u/admin/KEINASYSTEM_API_URL` | `https://main.keinafarm.net` | — |
|
||
| `u/admin/MAIL_FILTER_GMAIL_LAST_UID` | Gmail 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_GMAIL2_LAST_UID` | Gmail2 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER1_LAST_UID` | Xserver1 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER2_LAST_UID` | Xserver2 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER3_LAST_UID` | Xserver3 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER4_LAST_UID` | Xserver4 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER5_LAST_UID` | Xserver5 最終処理済み UID | — |
|
||
| `u/admin/MAIL_FILTER_XSERVER6_LAST_UID` | Xserver6 最終処理済み UID | — |
|
||
|
||
### 4.4 LAST_UID の仕組み
|
||
|
||
- **初回実行**: `LAST_UID` が `0` または未設定の場合、現在の最大 UID を記録して終了(既存メールを遡らない)
|
||
- **通常実行**: `LAST_UID + 1` 以降の UID を検索して処理
|
||
- **エラー時**: 個別メッセージの処理に失敗しても、成功した最大 UID まで更新する
|
||
|
||
### 4.5 LLM 判定(Gemini)
|
||
|
||
モデル: `gemini-2.0-flash`(`temperature=0`, `maxOutputTokens=10`)
|
||
|
||
プロンプトに渡す情報:
|
||
- 送信者アドレス・件名・本文冒頭
|
||
- 過去の同一送信者のフィードバック集計(`sender-context` API から取得)
|
||
|
||
回答: `1`(重要)/ `2`(重要でない)の1文字。`1` で始まる場合 `important`。
|
||
|
||
### 4.6 LINE 通知文フォーマット
|
||
|
||
```
|
||
📧 重要なメールが届きました
|
||
|
||
宛先: Gmail (メイン)
|
||
差出人: sender@example.com
|
||
件名: 件名テキスト
|
||
|
||
フィードバック:
|
||
https://main.keinafarm.net/mail/feedback/<uuid>
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 画面仕様(フロントエンド)
|
||
|
||
### 5.1 フィードバックページ(認証不要)
|
||
|
||
**URL**: `/mail/feedback/[token]`
|
||
**ファイル**: `frontend/src/app/mail/feedback/[token]/page.tsx`
|
||
|
||
LINE 通知のリンクから直接アクセス。JWT 認証不要のため、`api` インスタンス(JWT自動付与)ではなく素の `fetch` を使用。
|
||
|
||
**表示内容**:
|
||
- 送信者アドレス・件名・受信日時・本文冒頭
|
||
- フィードバックボタン: 重要だった / 普通のメール / 今後通知しない / 常に通知してほしい
|
||
- `今後通知しない` / `常に通知` 選択時: アドレス / ドメイン の適用範囲を選択
|
||
|
||
**状態**:
|
||
- 既にフィードバック済みの場合は現在の値をハイライト表示(再選択可能)
|
||
- 送信完了後「受け付けました」画面に切り替わる
|
||
|
||
---
|
||
|
||
### 5.2 メール処理履歴(JWT認証)
|
||
|
||
**URL**: `/mail/history`
|
||
**Navbar**: 「メール履歴」(History アイコン)
|
||
**ファイル**: `frontend/src/app/mail/history/page.tsx`
|
||
|
||
**表示内容**: 処理したメール最新100件
|
||
|
||
| カラム | 内容 |
|
||
|---|---|
|
||
| 受信日時 | 日時 + アカウント名 |
|
||
| 送信者 | メールアドレス |
|
||
| 件名 | テキスト(truncate)|
|
||
| LLM判定 | 重要(赤)/ 通常(灰)バッジ |
|
||
| フィードバック | 状態バッジ + 編集ボタン |
|
||
|
||
**フィルター**: アカウント別・LLM判定別セレクトボックス
|
||
|
||
**フィードバックモーダル**: 履歴画面からも4択でフィードバックを設定・変更できる。`never_notify` / `always_notify` 選択時はアドレス/ドメインの適用範囲を表示。
|
||
|
||
---
|
||
|
||
### 5.3 メール通知ルール管理(JWT認証)
|
||
|
||
**URL**: `/mail/rules`
|
||
**Navbar**: 「メールルール」(Shield アイコン)
|
||
**ファイル**: `frontend/src/app/mail/rules/page.tsx`
|
||
|
||
**追加フォーム**:
|
||
- 種別(アドレス / ドメイン)
|
||
- ルール(通知しない / 常に通知)
|
||
- 値(メールアドレスまたはドメイン名)
|
||
- メモ(任意)
|
||
|
||
**一覧表示**: 種別バッジ・ルールバッジ・値・メモ・設定日・削除ボタン
|
||
|
||
**ルールが自動追加される場面**: フィードバックで `never_notify` / `always_notify` を選択してスコープを指定すると自動登録される。
|
||
|
||
---
|
||
|
||
## 6. 本番環境の設定値
|
||
|
||
### バックエンド(`.env.production`)
|
||
|
||
```
|
||
MAIL_API_KEY=<Windmill の KEINASYSTEM_API_KEY と同じ値>
|
||
```
|
||
|
||
`settings.py` での参照:
|
||
```python
|
||
MAIL_API_KEY = os.environ.get('MAIL_API_KEY', '')
|
||
FRONTEND_URL = os.environ.get('FRONTEND_URL', 'http://localhost:3000')
|
||
```
|
||
|
||
`FRONTEND_URL` はフィードバック URL の生成に使用(本番値: `https://main.keinafarm.net`)。
|
||
|
||
### デプロイコマンド
|
||
|
||
```bash
|
||
# サーバー上で実行(--env-file を必ず指定)
|
||
cd /home/keinasystem/keinasystem_t02
|
||
git pull
|
||
docker compose -f docker-compose.prod.yml --env-file .env.production build
|
||
docker compose -f docker-compose.prod.yml --env-file .env.production up -d
|
||
```
|
||
|
||
**注意**: `--env-file .env.production` を省略すると SECRET_KEY 等が空になりバックエンドが起動しない。
|
||
|
||
### Windmill フローの更新手順
|
||
|
||
ローカルの `flows/mail_filter.flow.json` を編集後:
|
||
|
||
```bash
|
||
cd C:/Users/akira/Develop/windmill_workflow
|
||
# サーバーに転送
|
||
scp flows/mail_filter.flow.json keinafarm-claude:/tmp/mail_filter.flow.json
|
||
|
||
# サーバー上でデプロイ(Windmill API 経由)
|
||
ssh keinafarm-claude 'TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh; WM=http://172.18.0.15:8000/api/w/admins
|
||
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$WM/flows/delete/f/mail/mail_filter"
|
||
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @/tmp/mail_filter.flow.json "$WM/flows/create"'
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 設計判断と制約
|
||
|
||
### Windmill と KeinaSystem の疎結合
|
||
|
||
Windmill はスケジューラ・LLM呼び出し担当、KeinaSystem は DB・UI 担当として明確に分離。両者は REST API(APIキー認証)でのみ連携。
|
||
|
||
→ Windmill の障害が KeinaSystem のメイン機能(作付け計画等)に影響しない。
|
||
|
||
### APIキー認証の実装
|
||
|
||
`MailAPIKeyPermission`(`BasePermission` サブクラス)で `X-API-Key` ヘッダーを検証。`secrets.compare_digest` でタイミング攻撃を防止。
|
||
|
||
`authentication_classes = []` を明示的に設定したビューとしていないビューがある:
|
||
- `SenderRuleView`, `SenderContextView`: `authentication_classes = []` を設定済み → 403
|
||
- `MailEmailView` の POST: 設定なし(デフォルトの JWTAuthentication が動く)→ キー不一致時は 401
|
||
|
||
→ **統一するなら** `MailEmailView` にも `authentication_classes = []` を追加すべき(現状は動作上問題なし)。
|
||
|
||
### フィードバック URL のセキュリティ
|
||
|
||
UUID v4 のランダムトークンのみで認証。有効期限なし。LINE に送信された URL を知っている人なら誰でもフィードバックを送れる(悪用リスクは低いため許容)。
|
||
|
||
### 重複メール処理
|
||
|
||
同じメールが複数アカウントで受信される場合(転送設定等)、`message_id` の unique 制約で2件目以降を自動スキップ。最初に処理したアカウントの `account_code` でDBに記録される。
|
||
|
||
---
|
||
|
||
## 8. 運用手順
|
||
|
||
### 新しいメールアカウントを追加する場合
|
||
|
||
1. `flows/mail_filter.flow.json` の `ACCOUNTS` リストにエントリを追加
|
||
2. 本番 Windmill に Variables を追加(USER, PASSWORD, LAST_UID)
|
||
3. フローを再デプロイ
|
||
|
||
```bash
|
||
# Variable 追加例(サーバー上で実行)
|
||
ssh keinafarm-claude 'TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh; WM=http://172.18.0.15:8000/api/w/admins
|
||
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||
-d "{\"path\":\"u/admin/XSERVER7_IMAP_USER\",\"value\":\"newuser@keinafarm.com\",\"is_secret\":false,\"description\":\"\"}" \
|
||
"$WM/variables/create"'
|
||
```
|
||
|
||
### LINE トークンが期限切れになった場合
|
||
|
||
LINE Developers Console でトークンを再発行し、本番 Windmill の Variable を更新:
|
||
|
||
```bash
|
||
ssh keinafarm-claude 'TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh; WM=http://172.18.0.15:8000/api/w/admins
|
||
# 古いものを削除してから再作成
|
||
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$WM/variables/delete/u/admin/LINE_CHANNEL_ACCESS_TOKEN"
|
||
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||
-d "{\"path\":\"u/admin/LINE_CHANNEL_ACCESS_TOKEN\",\"value\":\"<新トークン>\",\"is_secret\":true,\"description\":\"LINE Messaging APIトークン\"}" \
|
||
"$WM/variables/create"'
|
||
```
|
||
|
||
### フローのログを確認する
|
||
|
||
```bash
|
||
# 最近のジョブ一覧
|
||
ssh keinafarm-claude 'TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh
|
||
curl -s -H "Authorization: Bearer $TOKEN" "http://172.18.0.15:8000/api/w/admins/jobs/completed/list?per_page=10" \
|
||
| grep -o "\"id\":\"[^\"]*\"\|\"started_at\":\"[^\"]*\"\|\"script_path\":\"[^\"]*\"" \
|
||
| paste - - - | grep mail_filter'
|
||
|
||
# ステップジョブのログ取得
|
||
ssh keinafarm-claude 'TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh; JOB_ID=<ステップジョブID>
|
||
curl -s -H "Authorization: Bearer $TOKEN" \
|
||
"http://172.18.0.15:8000/api/w/admins/jobs/completed/get/$JOB_ID" \
|
||
| grep -o "\"logs\":\"[^\"]*\"" | sed "s/\\\\n/\n/g"'
|
||
```
|
||
|
||
---
|
||
|
||
## 9. ソースファイル索引
|
||
|
||
### バックエンド
|
||
|
||
| ファイル | 内容 |
|
||
|---|---|
|
||
| `backend/apps/mail/models.py` | MailSender, MailEmail, MailNotificationToken |
|
||
| `backend/apps/mail/serializers.py` | MailSenderSerializer, MailEmailCreateSerializer, MailEmailListSerializer, FeedbackDetailSerializer |
|
||
| `backend/apps/mail/views.py` | 全ビュー(SenderRuleView, SenderContextView, MailEmailView, MailStatsView, FeedbackView, MailEmailFeedbackView, MailSenderViewSet)+ MailAPIKeyPermission |
|
||
| `backend/apps/mail/urls.py` | URL ルーティング |
|
||
| `backend/apps/mail/admin.py` | Django 管理画面登録 |
|
||
| `backend/apps/mail/migrations/` | マイグレーション |
|
||
| `backend/keinasystem/settings.py` | `MAIL_API_KEY`, `FRONTEND_URL` 設定(L161-162)|
|
||
| `backend/keinasystem/urls.py` | `path('api/mail/', include('apps.mail.urls'))` |
|
||
|
||
### フロントエンド
|
||
|
||
| ファイル | 内容 |
|
||
|---|---|
|
||
| `frontend/src/app/mail/feedback/[token]/page.tsx` | フィードバックページ(認証不要)|
|
||
| `frontend/src/app/mail/history/page.tsx` | メール処理履歴画面 |
|
||
| `frontend/src/app/mail/rules/page.tsx` | 送信者ルール管理画面 |
|
||
| `frontend/src/components/Navbar.tsx` | 「メール履歴」「メールルール」リンク追加済み |
|
||
|
||
### Windmill フロー
|
||
|
||
| ファイル | 内容 |
|
||
|---|---|
|
||
| `C:/Users/akira/Develop/windmill_workflow/flows/mail_filter.flow.json` | フロー定義(Python スクリプト本体を含む)|
|
||
|
||
本番 Windmill でのパス: `f/mail/mail_filter`
|
||
スケジュール: `f/mail/mail_filter_schedule`
|