diff --git a/internal/mail/imap.go b/internal/mail/imap.go index da22273..ab9bd12 100644 --- a/internal/mail/imap.go +++ b/internal/mail/imap.go @@ -356,7 +356,19 @@ func (w *IMAPWatcher) processMessage(c *imapclient.Client, buf *imapclient.Fetch return } - // 2. External rules: evaluate operator-configurable policy. + // 2. Idempotency: skip if message_id already exists. + existing, err := database.FindByMessageID(w.db, messageID) + if err != nil { + log.Error("imap: db lookup failed", "error", err) + return + } + if existing != nil { + log.Info("imap: duplicate message_id, skipping") + w.markSeen(c, buf.UID) + return + } + + // 3. External rules: evaluate operator-configurable policy. ruleResult := w.rules.Evaluate(rules.EmailContext{ Sender: sender, Subject: subject, @@ -369,18 +381,6 @@ func (w *IMAPWatcher) processMessage(c *imapclient.Client, buf *imapclient.Fetch return } - // 3. Idempotency: skip if message_id already exists. - existing, err := database.FindByMessageID(w.db, messageID) - if err != nil { - log.Error("imap: db lookup failed", "error", err) - return - } - if existing != nil { - log.Info("imap: duplicate message_id, skipping") - w.markSeen(c, buf.UID) - return - } - // 4. Threading: determine thread_id. threadID := messageID inReplyTo := "" diff --git a/requirements.md b/requirements.md index 9ea3f7c..26782fe 100644 --- a/requirements.md +++ b/requirements.md @@ -68,12 +68,12 @@ Danh sách trạng thái hợp lệ: - Nếu có `IMAP_PROXY_URL` thì kết nối IMAP qua proxy; nếu không thì direct. - Khi có email mới: 1. Anti-loop: nếu `sender` trùng email hệ thống -> `IGNORED`. - 2. Whitelist: nếu không nằm trong danh sách cho phép -> `IGNORED`. - 3. Idempotency: nếu `message_id` đã tồn tại -> bỏ qua. + 2. Idempotency: nếu `message_id` đã tồn tại -> bỏ qua. + 3. External rules: chạy pipeline các rule configurable (whitelist, blocklist, v.v.). Nếu reject -> `IGNORED`. Rule metadata được lưu vào `dispatch_context`. 4. Threading: - Nếu có `In-Reply-To`/`References`: tìm email cha trong DB, kế thừa `thread_id`. - Nếu không có: `thread_id = message_id`. - 5. Lưu DB với `status = RECEIVED`. + 5. Lưu DB với `status = RECEIVED`, bao gồm `dispatch_context` từ rules. 4.2 Dispatch (OpenClaw) - Lấy nội dung email hiện tại và history của cùng `thread_id` (chỉ các bản ghi `COMPLETED`). @@ -84,6 +84,7 @@ Danh sách trạng thái hợp lệ: - `history` (optional) - `callback_url` - `metadata.task_uuid` + - `metadata.*` từ `dispatch_context` (rule-derived context) - Cập nhật `status = AI_PROCESSING`. Retry OpenClaw: @@ -146,6 +147,7 @@ Required: - `OPENCLAW_URL`, `OPENCLAW_API_KEY` (nếu dùng) - `BRIDGE_CALLBACK_TOKEN` - `WHITELIST_EMAILS` (comma-separated) +- `BLOCKLIST_EMAILS` (comma-separated, optional) - `SYSTEM_EMAIL` - `IMAP_PROXY_URL` (optional)