fix: cap processed UIDs, move email docs into README, remove standalone guide
This commit is contained in:
parent
994f5601e9
commit
d223454a98
@ -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.
|
|
||||||
57
README.md
57
README.md
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines.
|
⚡️ 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
|
## 📢 News
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ nanobot agent -m "Hello from my local LLM!"
|
|||||||
|
|
||||||
## 💬 Chat Apps
|
## 💬 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 |
|
| Channel | Setup |
|
||||||
|---------|-------|
|
|---------|-------|
|
||||||
@ -174,6 +174,8 @@ Talk to your nanobot through Telegram, Discord, WhatsApp, or Feishu — anytime,
|
|||||||
| **Discord** | Easy (bot token + intents) |
|
| **Discord** | Easy (bot token + intents) |
|
||||||
| **WhatsApp** | Medium (scan QR) |
|
| **WhatsApp** | Medium (scan QR) |
|
||||||
| **Feishu** | Medium (app credentials) |
|
| **Feishu** | Medium (app credentials) |
|
||||||
|
| **DingTalk** | Medium (app credentials) |
|
||||||
|
| **Email** | Medium (IMAP/SMTP credentials) |
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Telegram</b> (Recommended)</summary>
|
<summary><b>Telegram</b> (Recommended)</summary>
|
||||||
@ -372,6 +374,55 @@ nanobot gateway
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Email</b></summary>
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
|
|
||||||
Config file: `~/.nanobot/config.json`
|
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)
|
- [ ] **Multi-modal** — See and hear (images, voice, video)
|
||||||
- [ ] **Long-term memory** — Never forget important context
|
- [ ] **Long-term memory** — Never forget important context
|
||||||
- [ ] **Better reasoning** — Multi-step planning and reflection
|
- [ ] **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
|
- [ ] **Self-improvement** — Learn from feedback and mistakes
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
|
|||||||
@ -55,7 +55,8 @@ class EmailChannel(BaseChannel):
|
|||||||
self.config: EmailConfig = config
|
self.config: EmailConfig = config
|
||||||
self._last_subject_by_chat: dict[str, str] = {}
|
self._last_subject_by_chat: dict[str, str] = {}
|
||||||
self._last_message_id_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:
|
async def start(self) -> None:
|
||||||
"""Start polling IMAP for inbound emails."""
|
"""Start polling IMAP for inbound emails."""
|
||||||
@ -301,6 +302,9 @@ class EmailChannel(BaseChannel):
|
|||||||
|
|
||||||
if dedupe and uid:
|
if dedupe and uid:
|
||||||
self._processed_uids.add(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:
|
if mark_seen:
|
||||||
client.store(imap_id, "+FLAGS", "\\Seen")
|
client.store(imap_id, "+FLAGS", "\\Seen")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user