Giờ ta chạm vào hộp thư thật. Bài này giúp bạn lấy được email mới một cách an toàn — nền tảng cho tầng Ingest và Normalize.
Chọn giao thức: IMAP hay Gmail API
| Tiêu chí | IMAP | Gmail API |
|---|---|---|
| Phổ quát (mọi provider) | Có | Chỉ Gmail |
| Gắn nhãn / label | Hạn chế | Mạnh, gốc rễ |
| Tạo nháp (draft) | Không chuẩn | Có endpoint riêng |
| Xác thực | App password | OAuth2 |
| Độ khó | Thấp | Trung bình |
Nguyên tắc xác thực an toàn
- Không bao giờ nhúng mật khẩu vào code. Dùng biến môi trường hoặc secret manager.
- Dùng App Password / OAuth, không dùng mật khẩu chính của tài khoản.
- Chỉ xin quyền tối thiểu. Với Gmail API,
gmail.modifyđủ để đọc + gắn nhãn + tạo nháp mà KHÔNG cho quyền xóa vĩnh viễn. - Tài khoản test riêng cho giai đoạn phát triển.
Ví dụ: lấy email chưa đọc qua IMAP (Python)
import imaplib, email, os
from email.header import decode_header
M = imaplib.IMAP4_SSL("imap.gmail.com")
M.login(os.environ["MAIL_USER"], os.environ["MAIL_APP_PASS"])
M.select("INBOX")
# Chỉ lấy email CHƯA đọc để tránh xử lý lặp
status, ids = M.search(None, "UNSEEN")
for num in ids[0].split()[:20]: # giới hạn 20/lần cho an toàn
_, data = M.fetch(num, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
subject = decode_header(msg["Subject"])[0][0]
print(subject, "|", msg["From"])
M.logout()
Ví dụ: liệt kê email mới qua Gmail API
results = service.users().messages().list(
userId="me",
q="is:unread newer_than:1d",
maxResults=20
).execute()
for m in results.get("messages", []):
full = service.users().messages().get(
userId="me", id=m["id"], format="full"
).execute()
# full["payload"]["headers"] chứa From, Subject...
Chuẩn hóa (Normalize) email
Email thô rất bẩn: HTML, chữ ký dài, chuỗi "Re: Re: Fwd:", trích dẫn thư cũ. Trước khi đưa vào LLM, hãy:
- Bóc plain text từ HTML (ưu tiên phần
text/plain). - Cắt phần trích dẫn thư cũ (mọi dòng bắt đầu bằng
>hoặc sau "On ... wrote:"). - Cắt chữ ký (thường sau
--). - Giới hạn độ dài body gửi vào LLM (vd 2000 ký tự đầu) để tiết kiệm token.
- Giữ metadata: from, subject, ngày, có đính kèm không.
Các bước thực hành
- Tạo App Password (Gmail) hoặc OAuth client (Gmail API).
- Lưu credential vào biến môi trường, thêm
.envvào.gitignore. - Viết hàm
fetch_unread(limit=20)trả về danh sách email đã chuẩn hóa. - Đánh dấu email đã xử lý (đọc, hoặc gắn nhãn
agent-seen) để không lặp. - Test: in ra 5 email chuẩn hóa, kiểm tra body sạch, không còn HTML.
Checklist kết nối
- [ ] Credential nằm ngoài code (env / secret).
- [ ] Dùng quyền tối thiểu (
gmail.modify, khôngfull). - [ ] Có cơ chế chống xử lý lặp (UNSEEN / label seen).
- [ ] Body đã bóc HTML + cắt trích dẫn + cắt chữ ký.
- [ ] Giới hạn số email mỗi lần chạy.
Sai lầm thường gặp
- Commit mật khẩu/token lên Git. Rò rỉ tức thì. Luôn dùng env +
.gitignore. - Xin quyền
gmail.fullhoặc quyền xóa khi chỉ cần đọc/gắn nhãn — vi phạm nguyên tắc quyền tối thiểu và rủi ro lớn nếu agent lỗi. - Gửi cả HTML thô vào LLM. Tốn token gấp 5–10 lần và làm LLM nhiễu, phân loại kém.
- Không chống lặp, khiến mỗi lần cron chạy lại xử lý cùng email — spam nháp, gắn nhãn trùng.
- Xử lý hàng trăm email cùng lúc ngay lần đầu. Giới hạn 20 để dễ quan sát và giới hạn thiệt hại nếu sai.
Học xong bài này rồi?
Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu,
và học hết khóa thì có chứng chỉ.
Lưu tiến độ của tôi