From b386ee43802d7bdffab04cd558a4ae16e4712e57 Mon Sep 17 00:00:00 2001 From: Akira Date: Wed, 25 Feb 2026 10:06:22 +0900 Subject: [PATCH] =?UTF-8?q?LAUDE.md=E3=81=AE=E6=9B=B4=E6=96=B0=E3=81=8C?= =?UTF-8?q?=E5=AE=8C=E4=BA=86=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=86=85=E5=AE=B9=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最終更新日 → 2026-02-25 プロジェクト構造 → mail/ と settings/password/ ページを追加 データモデル概要 → MailSender, MailEmail, MailNotificationToken を追加 実装状況 → メールフィルタリング機能を本番稼働済みに更新、パスワード変更機能を追加 マスタードキュメントリンク → document/11_マスタードキュメント_メール通知関連編.md を追加 トラブルシューティング → 本番デプロイコマンド(--env-file .env.production 必須)を冒頭に追加 更新履歴 → 今回の変更を記録 --- CLAUDE.md | 54 +- ...1_マスタードキュメント_メール通知関連編.md | 599 ++++++++++++++++++ 2 files changed, 646 insertions(+), 7 deletions(-) create mode 100644 document/11_マスタードキュメント_メール通知関連編.md diff --git a/CLAUDE.md b/CLAUDE.md index fa3f2cf..a0f2668 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Keina System - Claude 向けガイド -> **最終更新**: 2026-02-21 +> **最終更新**: 2026-02-25 > **現在のフェーズ**: Phase 1 (MVP) - 基本機能実装完了、試験中 ## 📌 このファイルの目的 @@ -83,7 +83,13 @@ keinasystem_t02/ ├── allocation/ # 作付け計画編集画面(メイン) ├── fields/ # 圃場一覧・詳細 ├── reports/ # 申請書ダウンロード - └── import/ # データ取込画面 + ├── import/ # データ取込画面 + ├── mail/ + │ ├── feedback/[token]/ # フィードバックページ(認証不要) + │ ├── history/ # メール処理履歴 + │ └── rules/ # 送信者ルール管理 + └── settings/ + └── password/ # パスワード変更 ``` --- @@ -126,6 +132,25 @@ Variety (品種マスタ) ├── crop (FK to Crop) ├── name (品種名) └── unique_together = ['crop', 'name'] + +MailSender (送信者ルール) +├── email (EmailField, nullable) +├── domain (CharField, nullable) +├── rule ('never_notify' | 'always_notify') +└── ConstraintCheck: email/domain どちらか一方のみ + +MailEmail (受信メール記録) +├── account (xserver/gmail/hotmail等) +├── message_id (unique) +├── sender_email, sender_domain +├── subject, body_preview +├── received_at, llm_verdict (important/not_important) +├── notified_at (LINE通知日時、nullable) +└── feedback (important/not_important/never_notify/always_notify, nullable) + +MailNotificationToken (フィードバックURL用トークン) +├── email (OneToOne FK to MailEmail) +└── token (UUID, unique) ``` ### 重要な設計判断 @@ -216,15 +241,19 @@ Variety (品種マスタ) - 共通 LinkModal コンポーネント 7. **メールフィルタリング機能**(Windmill連携): - Django `apps/mail` アプリ(MailSender, MailEmail, MailNotificationToken) - - Windmill向けAPI(APIキー認証): `GET /api/mail/sender-rule/`, `GET /api/mail/sender-context/`, `POST /api/mail/emails/` + - Windmill向けAPI(APIキー認証): `GET /api/mail/sender-rule/`, `GET /api/mail/sender-context/`, `POST /api/mail/emails/`, `GET /api/mail/stats/` - フィードバックAPI(認証不要・UUIDトークン): `GET/POST /api/mail/feedback//` - - ルール管理API(JWT認証): `GET/POST/DELETE /api/mail/senders/` + - ルール管理API(JWT認証): `GET/POST/DELETE /api/mail/senders/`, `PATCH /api/mail/emails//feedback/` - フィードバックページ: `/mail/feedback/[token]`(LINEからタップ一発、認証不要) - ルール管理ページ: `/mail/rules/` - 処理履歴ページ: `/mail/history/` - - 対応アカウント: Gmail(有効)、infoseek.jp(Outlook→Gmail転送で対応、To:ヘッダで判別)、Hotmail/Xserver(flow.jsonでenable可能) - - 仕様書: `document/メールフィルタ/mail_filter_spec.md` - - Windmill フロー: `f/mail/mail_filter`(ローカル: localhost, 本番: windmill.keinafarm.net — 本番デプロイ未実施) + - 対応アカウント: Gmail × 2、Xserver × 6(本番稼働中) + - Windmill フロー: `f/mail/mail_filter`(本番: windmill.keinafarm.net にデプロイ済み、10分間隔スケジュール) + - マスタードキュメント: `document/11_マスタードキュメント_メール通知関連編.md` +8. **パスワード変更機能**: + - Backend: `POST /api/auth/change-password/`(JWT認証、`ChangePasswordView` in `keinasystem/urls.py`) + - Frontend: `/settings/password` ページ + - Navbar: KeyRound アイコンボタン(ログアウトボタンの左隣) ### 🚧 既知の課題・技術的負債 @@ -277,6 +306,15 @@ Phase 2 のタスクに進む段階。 ## 🔍 トラブルシューティング +### 本番デプロイコマンド(必須) + +```bash +# ⚠️ --env-file .env.production を必ず付けること(省略するとSECRET_KEYが空でbackendが起動しない) +ssh keinafarm-claude 'cd /home/akira/keinasystem_t02 && \ + 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' +``` + ### マイグレーションエラー ```bash @@ -312,6 +350,7 @@ docker-compose exec backend python manage.py migrate ソースコード参照不要なレベルで記載されている。ソース確認が必要な場合もファイル名と行番号の索引がある。 - **圃場管理機能**: `document/10_マスタードキュメント_圃場管理編.md` +- **メール通知機能**: `document/11_マスタードキュメント_メール通知関連編.md` ### 設計ドキュメント(プロジェクト横断) @@ -336,6 +375,7 @@ docker-compose exec backend python manage.py migrate ## 📝 更新履歴 +- 2026-02-25: CLAUDE.md更新。パスワード変更機能追記。メールフィルタリング機能を本番稼働済みに更新。マスタードキュメント `document/11_マスタードキュメント_メール通知関連編.md` リンク追加。デプロイコマンド(`--env-file .env.production` 必須)をトラブルシューティングに追加 - 2026-02-22: メールフィルタリング機能を実装。`apps/mail` Django app、Windmill向けAPI(APIキー認証)、フィードバックページ、ルール管理ページを追加。仕様書: `document/メールフィルタ/mail_filter_spec.md` - 2026-02-21: マスタードキュメント体系を導入。`document/10_マスタードキュメント_圃場管理編.md` を追加。セッション推奨フローにマスタードキュメント参照を追加 - 2026-02-18: E-2(対応付け可視化・紐づけ管理)仕様追加。画面設計書・差異レポート・次タスク一覧を更新。完了済みタスク(A-8, D-1〜D-4, E-1)を既知の課題から除外 diff --git a/document/11_マスタードキュメント_メール通知関連編.md b/document/11_マスタードキュメント_メール通知関連編.md new file mode 100644 index 0000000..e16a446 --- /dev/null +++ b/document/11_マスタードキュメント_メール通知関連編.md @@ -0,0 +1,599 @@ +# マスタードキュメント - メール通知関連編 + +> **最終更新**: 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//` の URL をLINE通知文に含める。有効期限なし。 + +--- + +## 3. API仕様(バックエンド) + +ベース URL: `https://main.keinafarm.net/api/mail/` + +### 3.1 認証方式 + +| 認証方式 | 対象エンドポイント | ヘッダー | +|---|---|---| +| APIキー認証(Windmill用) | sender-rule, sender-context, POST emails/ | `X-API-Key: ` | +| JWT認証(フロントエンド用) | GET emails/, stats/, senders/, PATCH emails//feedback/ | `Authorization: Bearer ` | +| 認証不要 | GET/POST feedback// | なし | + +**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": "", + "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/"} +``` +`not_important` の場合: `{"id": 68}`(feedback_url なし) + +**重複処理**: `message_id` が既存の場合 400 を返す。Windmill 側で「重複メール、スキップ」として処理。 + +--- + +### 3.3 フィードバックエンドポイント(認証不要) + +#### GET /api/mail/feedback/\/ + +フィードバックページ表示用にメール情報を返す。 + +**レスポンス例**: +```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/\/ + +フィードバックを保存する。 + +**リクエストボディ**: +```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/\/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/\/ + +送信者ルール削除。 + +--- + +## 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/ +``` + +--- + +## 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= +``` + +`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`