From d223454a9885986b5c5a89c30fb3941b07457e40 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Mon, 9 Feb 2026 06:19:35 +0000 Subject: [PATCH] fix: cap processed UIDs, move email docs into README, remove standalone guide --- EMAIL_ASSISTANT_E2E_GUIDE.md | 164 ----------------------------------- README.md | 57 +++++++++++- nanobot/channels/email.py | 6 +- 3 files changed, 59 insertions(+), 168 deletions(-) delete mode 100644 EMAIL_ASSISTANT_E2E_GUIDE.md diff --git a/EMAIL_ASSISTANT_E2E_GUIDE.md b/EMAIL_ASSISTANT_E2E_GUIDE.md deleted file mode 100644 index a72a18c..0000000 --- a/EMAIL_ASSISTANT_E2E_GUIDE.md +++ /dev/null @@ -1,164 +0,0 @@ -# Nanobot Email Assistant: End-to-End Guide - -This guide explains how to run nanobot as a real email assistant with explicit user permission and optional automatic replies. - -## 1. What This Feature Does - -- Read unread emails via IMAP. -- Let the agent analyze/respond to email content. -- Send replies via SMTP. -- Enforce explicit owner consent before mailbox access. -- Let you toggle automatic replies on or off. - -## 2. Permission Model (Required) - -`channels.email.consentGranted` is the hard permission gate. - -- `false`: nanobot must not access mailbox content and must not send email. -- `true`: nanobot may read/send based on other settings. - -Only set `consentGranted: true` after the mailbox owner explicitly agrees. - -## 3. Auto-Reply Mode - -`channels.email.autoReplyEnabled` controls outbound automatic email replies. - -- `true`: inbound emails can receive automatic agent replies. -- `false`: inbound emails can still be read/processed, but automatic replies are skipped. - -Use `autoReplyEnabled: false` when you want analysis-only mode. - -## 4. Required Account Setup (Gmail Example) - -1. Enable 2-Step Verification in Google account security settings. -2. Create an App Password. -3. Use this app password for both IMAP and SMTP auth. - -Recommended servers: -- IMAP host/port: `imap.gmail.com:993` (SSL) -- SMTP host/port: `smtp.gmail.com:587` (STARTTLS) - -## 5. Config Example - -Edit `~/.nanobot/config.json`: - -```json -{ - "channels": { - "email": { - "enabled": true, - "consentGranted": true, - "imapHost": "imap.gmail.com", - "imapPort": 993, - "imapUsername": "you@gmail.com", - "imapPassword": "${NANOBOT_EMAIL_IMAP_PASSWORD}", - "imapMailbox": "INBOX", - "imapUseSsl": true, - "smtpHost": "smtp.gmail.com", - "smtpPort": 587, - "smtpUsername": "you@gmail.com", - "smtpPassword": "${NANOBOT_EMAIL_SMTP_PASSWORD}", - "smtpUseTls": true, - "smtpUseSsl": false, - "fromAddress": "you@gmail.com", - "autoReplyEnabled": true, - "pollIntervalSeconds": 30, - "markSeen": true, - "allowFrom": ["trusted.sender@example.com"] - } - } -} -``` - -## 6. Set Secrets via Environment Variables - -In the same shell before starting gateway: - -```bash -read -s "NANOBOT_EMAIL_IMAP_PASSWORD?IMAP app password: " -echo -read -s "NANOBOT_EMAIL_SMTP_PASSWORD?SMTP app password: " -echo -export NANOBOT_EMAIL_IMAP_PASSWORD -export NANOBOT_EMAIL_SMTP_PASSWORD -``` - -If you use one app password for both, enter the same value twice. - -## 7. Run and Verify - -Start: - -```bash -cd /Users/kaijimima1234/Desktop/nanobot -PYTHONPATH=/Users/kaijimima1234/Desktop/nanobot .venv/bin/nanobot gateway -``` - -Check channel status: - -```bash -PYTHONPATH=/Users/kaijimima1234/Desktop/nanobot .venv/bin/nanobot channels status -``` - -Expected behavior: -- `enabled=true + consentGranted=true + autoReplyEnabled=true`: read + auto reply. -- `enabled=true + consentGranted=true + autoReplyEnabled=false`: read only, no auto reply. -- `consentGranted=false`: no read, no send. - -## 8. Commands You Can Tell Nanobot - -Once gateway is running and email consent is enabled: - -1. Summarize yesterday's emails: - -```text -summarize my yesterday email -``` - -or - -```text -!email summary yesterday -``` - -2. Send an email to a friend: - -```text -!email send friend@example.com | Subject here | Body here -``` - -or - -```text -send email to friend@example.com subject: Subject here body: Body here -``` - -Notes: -- Sending command always performs a direct send (manual action by you). -- If `consentGranted` is `false`, send/read are blocked. -- If `autoReplyEnabled` is `false`, automatic replies are disabled, but direct send command above still works. - -## 9. End-to-End Test Plan - -1. Send a test email from an allowed sender to your mailbox. -2. Confirm nanobot receives and processes it. -3. If `autoReplyEnabled=true`, confirm a reply is delivered. -4. Set `autoReplyEnabled=false`, send another test email. -5. Confirm no auto-reply is sent. -6. Set `consentGranted=false`, send another test email. -7. Confirm nanobot does not read/send. - -## 10. Security Notes - -- Never commit real passwords/tokens into git. -- Prefer environment variables for secrets. -- Keep `allowFrom` restricted whenever possible. -- Rotate app passwords immediately if leaked. - -## 11. PR Checklist - -- [ ] `consentGranted` gating works for read/send. -- [ ] `autoReplyEnabled` toggle works as documented. -- [ ] README updated with new fields. -- [ ] Tests pass (`pytest`). -- [ ] No real credentials in tracked files. diff --git a/README.md b/README.md index 502a42f..8f7c1a2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines. -📏 Real-time line count: **3,448 lines** (run `bash core_agent_lines.sh` to verify anytime) +📏 Real-time line count: **3,479 lines** (run `bash core_agent_lines.sh` to verify anytime) ## 📢 News @@ -166,7 +166,7 @@ nanobot agent -m "Hello from my local LLM!" ## 💬 Chat Apps -Talk to your nanobot through Telegram, Discord, WhatsApp, or Feishu — anytime, anywhere. +Talk to your nanobot through Telegram, Discord, WhatsApp, Feishu, DingTalk, or Email — anytime, anywhere. | Channel | Setup | |---------|-------| @@ -174,6 +174,8 @@ Talk to your nanobot through Telegram, Discord, WhatsApp, or Feishu — anytime, | **Discord** | Easy (bot token + intents) | | **WhatsApp** | Medium (scan QR) | | **Feishu** | Medium (app credentials) | +| **DingTalk** | Medium (app credentials) | +| **Email** | Medium (IMAP/SMTP credentials) |
Telegram (Recommended) @@ -372,6 +374,55 @@ nanobot gateway
+
+Email + +Uses **IMAP** polling for inbound + **SMTP** for outbound. Requires explicit consent before accessing mailbox data. + +**1. Get credentials (Gmail example)** +- Enable 2-Step Verification in Google account security +- Create an [App Password](https://myaccount.google.com/apppasswords) +- Use this app password for both IMAP and SMTP + +**2. Configure** + +> [!TIP] +> Set `"autoReplyEnabled": false` if you only want to read/analyze emails without sending automatic replies. + +```json +{ + "channels": { + "email": { + "enabled": true, + "consentGranted": true, + "imapHost": "imap.gmail.com", + "imapPort": 993, + "imapUsername": "you@gmail.com", + "imapPassword": "your-app-password", + "imapUseSsl": true, + "smtpHost": "smtp.gmail.com", + "smtpPort": 587, + "smtpUsername": "you@gmail.com", + "smtpPassword": "your-app-password", + "smtpUseTls": true, + "fromAddress": "you@gmail.com", + "allowFrom": ["trusted@example.com"] + } + } +} +``` + +> `consentGranted`: Must be `true` to allow mailbox access. Set to `false` to disable reading and sending entirely. +> `allowFrom`: Leave empty to accept emails from anyone, or restrict to specific sender addresses. + +**3. Run** + +```bash +nanobot gateway +``` + +
+ ## ⚙️ Configuration Config file: `~/.nanobot/config.json` @@ -542,7 +593,7 @@ PRs welcome! The codebase is intentionally small and readable. 🤗 - [ ] **Multi-modal** — See and hear (images, voice, video) - [ ] **Long-term memory** — Never forget important context - [ ] **Better reasoning** — Multi-step planning and reflection -- [ ] **More integrations** — Discord, Slack, email, calendar +- [ ] **More integrations** — Slack, calendar, and more - [ ] **Self-improvement** — Learn from feedback and mistakes ### Contributors diff --git a/nanobot/channels/email.py b/nanobot/channels/email.py index 029c00d..0e47067 100644 --- a/nanobot/channels/email.py +++ b/nanobot/channels/email.py @@ -55,7 +55,8 @@ class EmailChannel(BaseChannel): self.config: EmailConfig = config self._last_subject_by_chat: dict[str, str] = {} self._last_message_id_by_chat: dict[str, str] = {} - self._processed_uids: set[str] = set() + self._processed_uids: set[str] = set() # Capped to prevent unbounded growth + self._MAX_PROCESSED_UIDS = 100000 async def start(self) -> None: """Start polling IMAP for inbound emails.""" @@ -301,6 +302,9 @@ class EmailChannel(BaseChannel): if dedupe and uid: self._processed_uids.add(uid) + # mark_seen is the primary dedup; this set is a safety net + if len(self._processed_uids) > self._MAX_PROCESSED_UIDS: + self._processed_uids.clear() if mark_seen: client.store(imap_id, "+FLAGS", "\\Seen")