- Skip task_uuid key when merging DispatchContext into dispatch metadata - Update requirements.md acceptance criteria and test matrix for external rules Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
184 lines
6.2 KiB
Markdown
184 lines
6.2 KiB
Markdown
PROJECT: EMAIL-OPENCLAW BRIDGE (STATEFUL, KISS)
|
|
|
|
1) Mục tiêu & phạm vi
|
|
|
|
Mục tiêu hệ thống:
|
|
- Tự động xử lý email bằng OpenClaw theo luồng:
|
|
Ingress (IMAP) -> Dispatch (OpenClaw) -> Callback -> Egress (SMTP reply).
|
|
|
|
Phạm vi v1:
|
|
- Stateful bằng SQLite.
|
|
- Hỗ trợ email thread theo `thread_id`.
|
|
- Idempotency theo `message_id`.
|
|
- Webhook callback có xác thực bằng token đơn giản.
|
|
- Bổ sung health endpoints:
|
|
- `GET /healthz` (liveness)
|
|
- `GET /readyz` (readiness)
|
|
- Retry cơ bản (KISS) cho OpenClaw và SMTP:
|
|
- Tối đa 3 lần với backoff: 1s, 5s, 15s.
|
|
|
|
Non-goals v1:
|
|
- Không rate limit.
|
|
- Không DLQ/reprocess UI.
|
|
- Không stack metrics nâng cao.
|
|
|
|
2) Công nghệ yêu cầu
|
|
|
|
- Ngôn ngữ: Go.
|
|
- Database: SQLite.
|
|
- Khuyến nghị: `gorm.io/gorm` + `modernc.org/sqlite` (không cần CGO khi build Docker).
|
|
- IMAP: `github.com/emersion/go-imap` (+ IDLE).
|
|
- SMTP: `net/smtp` hoặc thư viện gửi mail Go tương đương.
|
|
- HTTP/Webhook: `gin-gonic/gin` hoặc `gofiber/fiber`.
|
|
- Proxy IMAP (optional): `golang.org/x/net/proxy`.
|
|
- Triển khai: Docker + Docker Compose.
|
|
|
|
3) Database schema
|
|
|
|
Bảng `tasks`:
|
|
- `id` INTEGER PRIMARY KEY AUTOINCREMENT
|
|
- `task_uuid` TEXT UNIQUE
|
|
- `thread_id` TEXT
|
|
- `message_id` TEXT
|
|
- `sender` TEXT
|
|
- `subject` TEXT
|
|
- `body_plain` TEXT
|
|
- `ai_response` TEXT
|
|
- `status` TEXT
|
|
- `attempt_openclaw` INTEGER DEFAULT 0
|
|
- `attempt_smtp` INTEGER DEFAULT 0
|
|
- `last_error` TEXT NULL
|
|
- `next_retry_at` DATETIME NULL (chỉ cần nếu retry xử lý theo scheduler/worker)
|
|
- `created_at` DATETIME
|
|
- `updated_at` DATETIME
|
|
|
|
Danh sách trạng thái hợp lệ:
|
|
- `IGNORED`
|
|
- `RECEIVED`
|
|
- `AI_PROCESSING`
|
|
- `CALLBACK_DONE`
|
|
- `SMTP_RETRYING`
|
|
- `COMPLETED`
|
|
- `FAILED`
|
|
|
|
4) Luồng xử lý chi tiết
|
|
|
|
4.1 Ingress (IMAP)
|
|
- Theo dõi mailbox qua IMAP IDLE.
|
|
- 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. 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`, 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`).
|
|
- Gọi OpenClaw API bằng HTTP client đi direct (không kế thừa proxy hệ thống).
|
|
- Payload tối thiểu:
|
|
- `input`
|
|
- `session_id` = `thread_id`
|
|
- `history` (optional)
|
|
- `callback_url`
|
|
- `metadata.task_uuid`
|
|
- `metadata.*` từ `dispatch_context` (rule-derived context)
|
|
- Cập nhật `status = AI_PROCESSING`.
|
|
|
|
Retry OpenClaw:
|
|
- Khi lỗi tạm thời: retry tối đa 3 lần theo backoff 1s, 5s, 15s.
|
|
- Mỗi lần retry tăng `attempt_openclaw`.
|
|
- Hết retry: `status = FAILED`, ghi `last_error`.
|
|
|
|
4.3 Callback + Egress (SMTP)
|
|
- Webhook nhận callback từ OpenClaw.
|
|
- Tìm task theo `metadata.task_uuid`.
|
|
- Nếu task đã `COMPLETED`: trả 200 và không cập nhật gì thêm (idempotent callback).
|
|
- Nếu hợp lệ: cập nhật `ai_response`, `status = CALLBACK_DONE`.
|
|
|
|
Gửi email phản hồi:
|
|
- Subject/Body theo kết quả AI.
|
|
- Bắt buộc set `In-Reply-To` và `References` khớp message liên quan để giữ thread phía người nhận.
|
|
|
|
Retry SMTP:
|
|
- Nếu lỗi tạm thời: chuyển `SMTP_RETRYING`, retry 3 lần theo 1s, 5s, 15s.
|
|
- Mỗi lần retry tăng `attempt_smtp`.
|
|
- Hết retry: `status = FAILED`, ghi `last_error`.
|
|
- Thành công: `status = COMPLETED`.
|
|
|
|
5) API contract & bảo mật
|
|
|
|
5.1 Callback contract (từ OpenClaw)
|
|
Required:
|
|
- `metadata.task_uuid`
|
|
- `result` (text)
|
|
- `status`
|
|
|
|
5.2 Xác thực webhook (KISS)
|
|
- Yêu cầu header: `X-Bridge-Token`.
|
|
- Giá trị kỳ vọng lấy từ `.env`, ví dụ: `BRIDGE_CALLBACK_TOKEN`.
|
|
- Thiếu/sai token -> trả `401 Unauthorized`.
|
|
|
|
5.3 Health endpoints
|
|
- `GET /healthz` -> `200 {"status":"ok"}` nếu process sống.
|
|
- `GET /readyz`:
|
|
- kiểm tra DB writable,
|
|
- kiểm tra cấu hình bắt buộc có đủ (IMAP/SMTP/OpenClaw/token),
|
|
- nếu lỗi bất kỳ check nào -> `503`.
|
|
|
|
6) Logging & observability (mức tối thiểu)
|
|
|
|
- Structured logging ra stdout.
|
|
- Mỗi log liên quan xử lý email phải có:
|
|
- `task_uuid`
|
|
- `thread_id`
|
|
- `message_id`
|
|
- `status`
|
|
- `attempt`
|
|
- `error` (nếu có)
|
|
|
|
7) Cấu hình môi trường
|
|
|
|
Đọc từ `.env`:
|
|
- `IMAP_HOST`, `IMAP_PORT`, `IMAP_USER`, `IMAP_PASS`
|
|
- `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`
|
|
- `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)
|
|
|
|
Ví dụ:
|
|
- `WHITELIST_EMAILS="user1@example.com,user2@example.com"`
|
|
- `IMAP_PROXY_URL="socks5://user:pass@proxy-host:port"`
|
|
|
|
8) Docker triển khai
|
|
|
|
- Multi-stage build để tối ưu image size.
|
|
- `docker-compose.yml` mount volume cho SQLite DB (vd `database.db`).
|
|
- Xem log bằng: `docker logs -f <container_name>`.
|
|
|
|
9) Acceptance criteria (bắt buộc đạt)
|
|
|
|
1. Email hợp lệ đi hết pipeline và kết thúc `COMPLETED`.
|
|
2. Thread reply giữ đúng `In-Reply-To`/`References`.
|
|
3. OpenClaw/SMTP retry đúng thứ tự 1s -> 5s -> 15s.
|
|
4. Hết retry thì `FAILED` và có `last_error`.
|
|
5. Callback sai token trả 401 và không đổi state task.
|
|
6. Anti-loop + idempotency + external rules hoạt động đúng thứ tự.
|
|
|
|
10) Test matrix tối thiểu
|
|
|
|
1. Happy path: mail mới -> callback hợp lệ -> SMTP thành công -> `COMPLETED`.
|
|
2. OpenClaw fail 2 lần, lần 3 thành công -> `COMPLETED`, `attempt_openclaw = 3`.
|
|
3. SMTP fail hết retry -> `FAILED`, có `last_error`.
|
|
4. Callback sai token -> 401, state không đổi.
|
|
5. Duplicate `message_id` -> không tạo task mới.
|
|
6. Sender là chính hệ thống -> `IGNORED`.
|
|
7. Sender ngoài whitelist -> `IGNORED` bởi external rules.
|
|
8. Sender trong blocklist -> `IGNORED` bởi external rules.
|
|
9. Rule metadata được truyền qua dispatch context đến OpenClaw.
|