Initial commit: email-openclaw bridge v1

Full pipeline: IMAP ingress -> OpenClaw dispatch -> callback -> SMTP reply.
SQLite stateful storage with idempotency, threading, and retry logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 18:16:57 +07:00
co-authored by Claude Opus 4.7
commit 8815c20c13
35 changed files with 2808 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
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. 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.
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`.
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`
- 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``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)
- `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 + whitelist + idempotency hoạt động đúng.
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 hoặc ngoài whitelist -> `IGNORED`.