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>
83 lines
3.0 KiB
Markdown
83 lines
3.0 KiB
Markdown
# Checkpoint 002 — IMAP Ingress
|
|
|
|
**Ngày:** 2026-04-24
|
|
**Trạng thái:** ✅ Hoàn thành
|
|
**Verify:** `go build ./...` thành công
|
|
|
|
---
|
|
|
|
## Việc đã làm
|
|
|
|
### 1. Task Repository (`internal/database/repository.go`)
|
|
Thêm các helper function cho CRUD operations:
|
|
- `FindByMessageID` — lookup cho idempotency check
|
|
- `FindLatestByThreadID` — tìm task COMPLETED gần nhất trong thread
|
|
- `FindByTaskUUID` — lookup cho callback handler
|
|
- `ThreadHistory` — lấy toàn bộ history thread (cho OpenClaw context)
|
|
- `CreateTask`, `UpdateTask` — CRUD cơ bản
|
|
|
|
### 2. IMAP Watcher (`internal/mail/imap.go`)
|
|
Module chính cho Ingress, bao gồm:
|
|
|
|
**Kết nối & IDLE:**
|
|
- `DialTLS` → Login → Select INBOX
|
|
- IDLE loop với timer 28 phút (RFC recommends < 29 min)
|
|
- Auto-reconnect sau 10s khi mất kết nối
|
|
- Graceful shutdown qua `context.Context`
|
|
- `UnilateralDataHandler` nhận notification khi có email mới
|
|
|
|
**Xử lý email (đúng 5 bước theo requirements 4.1):**
|
|
1. **Anti-loop**: sender == `SYSTEM_EMAIL` → `IGNORED`
|
|
2. **Whitelist**: sender không trong danh sách → `IGNORED` (empty whitelist = cho phép tất cả)
|
|
3. **Idempotency**: `message_id` đã tồn tại → skip
|
|
4. **Threading**: parse `In-Reply-To` header → tìm parent task → kế thừa `thread_id`
|
|
5. **Save**: tạo task `status = RECEIVED`
|
|
|
|
**Extras:**
|
|
- Fetch UNSEEN messages on startup (xử lý email đến trong lúc offline)
|
|
- Mark `\Seen` sau khi xử lý xong
|
|
- Lưu task IGNORED vào DB cho audit
|
|
- Parse body parts qua `go-message` để lấy plain text
|
|
- Hook `OnReceived` cho dispatch (chưa wire)
|
|
|
|
### 3. Entrypoint (`cmd/bridge/main.go`)
|
|
- Chạy IMAP watcher + HTTP server concurrently
|
|
- Graceful shutdown qua SIGINT/SIGTERM
|
|
- `sync.WaitGroup` đợi cả hai goroutine
|
|
|
|
---
|
|
|
|
## Dependencies mới
|
|
|
|
```
|
|
github.com/emersion/go-imap/v2 v2.0.0-beta.8
|
|
github.com/emersion/go-message v0.18.2
|
|
github.com/google/uuid (for task_uuid generation)
|
|
```
|
|
|
|
## Quyết định thiết kế
|
|
|
|
| Quyết định | Lý do |
|
|
|---|---|
|
|
| go-imap v2 thay vì v1 | IDLE tích hợp sẵn, API hiện đại hơn, dự án mới nên dùng v2 |
|
|
| IDLE timer 28 min | RFC recommends < 29 min, close + reopen IDLE để tránh server timeout |
|
|
| Fetch UNSEEN on startup | Xử lý email đến lúc bridge offline |
|
|
| Empty whitelist = allow all | UX tốt hơn: không cấu hình whitelist thì mặc định chấp nhận mọi sender |
|
|
| Lưu IGNORED vào DB | Auditability: biết email nào bị bỏ qua và lý do |
|
|
| `UnilateralDataHandler` | Nhận notification realtime khi mailbox thay đổi (không cần polling) |
|
|
|
|
---
|
|
|
|
## Files thay đổi
|
|
|
|
- **Mới:** `internal/database/repository.go`
|
|
- **Mới:** `internal/mail/imap.go`
|
|
- **Cập nhật:** `cmd/bridge/main.go` (IMAP watcher + graceful shutdown)
|
|
- **Xóa:** `internal/mail/mail.go` (stub cũ)
|
|
|
|
## Tiếp theo
|
|
|
|
1. **OpenClaw Dispatch** — wire `OnReceived` → HTTP POST to OpenClaw
|
|
2. **Callback handler** — `POST /callback` + token auth
|
|
3. **SMTP Egress** — gửi reply với In-Reply-To/References
|