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 `. 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.