zohomail 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zohomail-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alphaolomi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: zohomail
3
+ Version: 1.1.0
4
+ Summary: Local Zoho Mail mirror: IMAP sync into SQLite, a CLI, and an MCP server
5
+ Author-email: alphaolomi <10551599+alphaolomi@users.noreply.github.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 alphaolomi
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/alphaolomi/zoho-mail
29
+ Project-URL: Repository, https://github.com/alphaolomi/zoho-mail
30
+ Project-URL: Issues, https://github.com/alphaolomi/zoho-mail/issues
31
+ Project-URL: Changelog, https://github.com/alphaolomi/zoho-mail/releases
32
+ Keywords: zoho,email,imap,smtp,mcp
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Environment :: Console
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Topic :: Communications :: Email
44
+ Requires-Python: >=3.10
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Dynamic: license-file
48
+
49
+ # zohomail
50
+
51
+ An AI email client for Zoho Mail: **IMAP / POP3 / SMTP** access with an app-specific
52
+ password, a **local SQLite mirror** of the mailbox with full-text search and
53
+ deduplicated attachment storage, an **MCP server**, and **Claude Code skills and
54
+ subagents** that read, triage, and draft against it.
55
+
56
+ Standard library only: no third-party packages, no OAuth app, no API key.
57
+ MIT licensed. Mail, passwords and rules stay in a profile outside this
58
+ repository, so the tree itself can be shared.
59
+
60
+ Verified against a Zoho organisation account on the `zoho.com` data centre.
61
+
62
+ ```bash
63
+ pip install -e . # from a checkout; or pip install .
64
+ zohomail doctor # prove connectivity
65
+ zohomail sync # mirror the last 90 days into SQLite
66
+ zohomail db stats # what's local now
67
+ ```
68
+
69
+ `python -m zohomail` is the same program. From this directory it works
70
+ without installing; `pip install` is what puts `zohomail` on `PATH` for
71
+ any other directory, and what a published install uses.
72
+
73
+ Then, in Claude Code: *"triage my inbox"*, *"what am I waiting on?"*,
74
+ *"draft a reply to the bank integration thread"*.
75
+
76
+ ---
77
+
78
+ ## Setup
79
+
80
+ 1. **Install.** From a checkout, `pip install -e .`. Once the repository is
81
+ public, `pip install git+https://github.com/alphaolomi/zoho-mail.git`
82
+ installs the engine without the Claude skills. The skills, agents and
83
+ `scripts/` stay with the clone; they are not part of the wheel.
84
+ 2. **Generate an app-specific password** at
85
+ [accounts.zoho.com → Security → App Passwords](https://accounts.zoho.com/home#security/apppassword).
86
+ Not your account password; it bypasses 2FA for mail protocols only.
87
+ 3. **Enable the protocols** in Zoho Mail → Settings → Mail Accounts → IMAP / POP Access.
88
+ 4. **Create your profile** — your config, rules and mail data live outside the
89
+ repo, in `~/.zohomail/<name>/` (see [Profiles](#profiles)):
90
+
91
+ ```bash
92
+ zohomail profile init alice --email you@yourdomain.com
93
+ zohomail profile use alice
94
+ ```
95
+
96
+ Then fill in `~/.zohomail/alice/config.env`:
97
+
98
+ ```ini
99
+ ZOHO_EMAIL=you@yourdomain.com
100
+ ZOHO_APP_PASSWORD=xxxxxxxxxxxx
101
+ ZOHO_DC=com # com | eu | in | au | jp | ca | sa | uk
102
+ ZOHO_HOST_STYLE=standard # standard -> imap.zoho.com | pro -> imappro.zoho.com
103
+ SYNC_DAYS=90
104
+ SYNC_FOLDERS=INBOX,Sent # names or globs: "*", "INBOX/*", "!INBOX/Spammy"
105
+ BULK_FOLDERS= # alert folders your Zoho filters fill (see below)
106
+ ```
107
+
108
+ 5. **Write your rules** in `~/.zohomail/alice/rules.md`: who matters, what is
109
+ urgent, how replies should sound. The skills read it before they act.
110
+ 6. **Check it** — `zohomail doctor` probes both host styles across all
111
+ three protocols and reports which authenticate.
112
+
113
+ Environment variables override `config.env`.
114
+
115
+ ---
116
+
117
+ ## Profiles
118
+
119
+ The repo is the engine: code, schema, skills, agents and scripts, with nothing
120
+ personal in it. Each person, or each mailbox, gets a profile:
121
+
122
+ ```
123
+ ~/.zohomail/<name>/
124
+ config.env account, app password, folders, bulk folders, mute list
125
+ rules.md their triage priorities, key people, drafting voice, hard limits
126
+ store/ mail.db and attachment blobs
127
+ state/ poll cursors and scheduler logs
128
+ ```
129
+
130
+ The active profile is, first match wins: `--profile NAME`, `ZOHOMAIL_HOME=<dir>`,
131
+ `ZOHOMAIL_PROFILE=<name>`, the one set by `profile use`, then a legacy `.env` in
132
+ the repo, then `default`. Set `ZOHOMAIL_PROFILES` to keep profiles somewhere
133
+ other than `~/.zohomail`.
134
+
135
+ ```bash
136
+ python -m zohomail profile list # * marks the active one
137
+ python -m zohomail profile show # where its config, rules and store are
138
+ python -m zohomail profile rules # what the skills read
139
+ python -m zohomail --profile technical sync
140
+ python -m zohomail.mcp_server --profile technical
141
+ ```
142
+
143
+ Two people on two machines each run `profile init` once. Two mailboxes on one
144
+ machine (your own and a shared `technical@`, say) are two profiles; pick one
145
+ per command with `--profile`, or register scheduled tasks per profile with
146
+ `-MailProfile`.
147
+
148
+ **Upgrading from the in-repo layout.** Older installs kept `.env`, `store/` and
149
+ `state/` inside the repo. That still works, and one command moves it out:
150
+
151
+ ```bash
152
+ python -m zohomail profile migrate alice
153
+ ```
154
+
155
+ It copies `.env` to `config.env`, moves `store/` and `state/`, adds a rules
156
+ template, and makes `alice` active. The repo `.env` is removed only after
157
+ everything else has moved; stop the MCP server first so the store is not open.
158
+
159
+ ### Servers
160
+
161
+ | Protocol | Standard | Pro (some org accounts) | Port | Security |
162
+ |---|---|---|---|---|
163
+ | IMAP | `imap.zoho.com` | `imappro.zoho.com` | 993 | SSL |
164
+ | POP3 | `pop.zoho.com` | `poppro.zoho.com` | 995 | SSL |
165
+ | SMTP | `smtp.zoho.com` | `smtppro.zoho.com` | 465 | SSL |
166
+
167
+ `ZOHO_SMTP_PORT=587` switches to STARTTLS automatically.
168
+
169
+ ---
170
+
171
+ ## The local store
172
+
173
+ `python -m zohomail sync` mirrors IMAP into the profile's `store/mail.db`. Sync is incremental —
174
+ each folder keeps a UID cursor, and a UIDVALIDITY change invalidates it correctly.
175
+
176
+ ```bash
177
+ python -m zohomail sync # configured folders, SYNC_DAYS window
178
+ python -m zohomail sync --days 365 --full # backfill a year
179
+ python -m zohomail sync --folders "INBOX,Sent,Newsletter"
180
+ python -m zohomail sync --folders "INBOX/*" # every folder under INBOX
181
+ python -m zohomail sync --folders "*" --max 500 # every folder, capped
182
+ python -m zohomail db stats # counts, range, per-folder cursors
183
+ ```
184
+
185
+ ### Folders and hierarchy
186
+
187
+ Folders are stored under their full server path (`INBOX/Sentry`), so the store
188
+ mirrors the hierarchy you see in Zoho. `SYNC_FOLDERS`, `--folders` and
189
+ `search --folder` all take the same selectors:
190
+
191
+ | Selector | Picks |
192
+ |---|---|
193
+ | `INBOX/Sentry` | that folder (case does not matter) |
194
+ | `Sentry` | the one folder whose path ends `/Sentry`; an error if two do |
195
+ | `Sent`, `Spam`, `Trash`, `Drafts` | the server's special-use folder, whatever it is called |
196
+ | `INBOX/*` | every folder under INBOX, but not INBOX itself |
197
+ | `*` | every folder except Trash, Spam and Drafts (name those to include them) |
198
+ | `!INBOX/Wazuh` | removes a folder the other entries picked |
199
+
200
+ **Bulk folders.** Zoho filters often route alerts (Wazuh, Sentry, CloudWatch)
201
+ into their own folders. List those in `BULK_FOLDERS` and their messages are
202
+ marked automated, so `--no-bots`, the triage queue and briefs skip them. Header
203
+ heuristics miss senders such as UptimeRobot; the folder does not. A bulk
204
+ folder's first sync reaches back only `BULK_SYNC_DAYS` (default 30, `0` = the
205
+ normal window, `--bulk-days` per run). New alerts are always kept.
206
+
207
+ ```ini
208
+ SYNC_FOLDERS=*
209
+ BULK_FOLDERS=INBOX/Wazuh,INBOX/AWS Notifications,INBOX/Sentry,INBOX/Grafana
210
+ BULK_SYNC_DAYS=30
211
+ ```
212
+
213
+ ### Entities
214
+
215
+ ```
216
+ accounts ── folders ── messages ── message_addresses ── addresses
217
+ │
218
+ ├── attachments (sha256-deduped blobs on disk)
219
+ ├── message_refs (Message-ID graph for threading)
220
+ ├── message_labels ── labels
221
+ ├── analyses (what the AI concluded)
222
+ └── threads ── tasks, drafts
223
+ ```
224
+
225
+ - **Threading** stitches messages by `In-Reply-To`/`References` first, merging
226
+ threads when a late message joins two chains. Subject matching is only a
227
+ fallback, and only when a participant overlaps — so two unrelated "Re: Hello"
228
+ chains never merge.
229
+ - **Attachments** are content-addressed: bytes go to `store/blobs/<aa>/<sha256>`,
230
+ so the same signature logo across 40 messages is stored once. Inline parts are
231
+ flagged so you can filter for real documents. Files over `MAX_ATTACHMENT_MB`
232
+ are indexed without their bytes.
233
+ - **Full-text search** is FTS5 over subject, body, sender name and address, kept
234
+ current by triggers and ranked with bm25.
235
+
236
+ ### Reading it
237
+
238
+ ```bash
239
+ python -m zohomail search "invoice NOT draft" --no-bots
240
+ python -m zohomail search --sender priya --attachments
241
+ python -m zohomail show 12 # one message, full body
242
+ python -m zohomail thread 10 # the whole conversation
243
+ python -m zohomail threads --awaiting-reply # you wrote last, silence since
244
+ python -m zohomail attachments --name .pdf --extract ./out
245
+ ```
246
+
247
+ Every command takes `--json` for machine consumption.
248
+
249
+ FTS5 syntax: `invoice payment` (AND), `"purchase order"` (phrase),
250
+ `invoice OR receipt`, `deploy NOT staging`, `integrat*` (prefix).
251
+
252
+ ---
253
+
254
+ ## The AI layer
255
+
256
+ The Python holds no model calls. **Claude Code does the reasoning**, and the store
257
+ is its memory — so conclusions survive across sessions and accumulate.
258
+
259
+ | Table | Holds |
260
+ |---|---|
261
+ | `analyses` | priority, category, summary, action items, needs-reply, confidence |
262
+ | `labels` | topic/category tags applied to messages |
263
+ | `tasks` | extracted work with due dates and status |
264
+ | `drafts` | proposed replies awaiting your approval |
265
+
266
+ ```bash
267
+ python -m zohomail queue # messages with no analysis yet
268
+ python -m zohomail annotate --message 12 --priority high --category partner-integration \
269
+ --summary "The bank needs the sandbox endpoints before they can test" \
270
+ --action "Send Priya the endpoint list" --needs-reply --label acme-bank
271
+ python -m zohomail task add --title "Send Acme Bank endpoints" --message 12 --due 2026-09-18
272
+ python -m zohomail task # open tasks by due date
273
+ ```
274
+
275
+ ### Skills
276
+
277
+ In `.claude/skills/`, available in any Claude Code session in this directory:
278
+
279
+ | Skill | Use |
280
+ |---|---|
281
+ | `mail-triage` | Work the queue: judge, annotate, label, create tasks |
282
+ | `mail-search` | Find mail, answer questions from it, pull attachments |
283
+ | `mail-draft` | Draft a reply in your voice, saved for review |
284
+ | `mail-brief` | A read briefing: what needs you, what you're waiting on |
285
+
286
+ ### Subagents
287
+
288
+ In `.claude/agents/` — dispatchable, each running in its own context:
289
+
290
+ | Agent | Does | Never |
291
+ |---|---|---|
292
+ | `mail-triage-agent` | Syncs, triages the queue, files tasks, reports | sends, deletes, marks read |
293
+ | `mail-reply-drafter` | Reads the thread, matches your voice, drafts | sends |
294
+ | `mail-archivist` | Syncs/backfills, labels, finds noise and stale threads | sends, deletes server mail |
295
+
296
+ ### MCP server
297
+
298
+ `.mcp.json` registers a stdio MCP server exposing 18 tools — `mail_search`,
299
+ `mail_message`, `mail_thread`, `mail_threads`, `mail_queue`, `mail_stats`,
300
+ `mail_attachments`, `mail_attachment_extract`, `mail_annotate`, `mail_task_add`,
301
+ `mail_tasks`, `mail_task_status`, `mail_draft_create`, `mail_drafts`,
302
+ `mail_draft_get`, `mail_draft_send`, `mail_sync`, `mail_flag`.
303
+
304
+ ```bash
305
+ python -m zohomail.mcp_server # stdio; any MCP client can launch it
306
+ ```
307
+
308
+ Point Claude Desktop or another client at the same command to query this mailbox
309
+ from outside Claude Code.
310
+
311
+ ### Sending is always gated
312
+
313
+ Drafts are written to the store, never sent automatically:
314
+
315
+ ```bash
316
+ python -m zohomail draft new --to priya@vendor.example --subject "RE: ..." \
317
+ --body-file reply.txt --thread 10 --reply-to 12 --rationale "unblocks testing"
318
+ python -m zohomail draft show 1
319
+ python -m zohomail draft send 1 --yes # --yes is required
320
+ ```
321
+
322
+ Without `--yes` the CLI refuses and exits 2. The MCP `mail_draft_send` tool
323
+ requires `confirm: true`. Both mean *the user said send it* — not the model's own
324
+ confidence. The agents are instructed never to send.
325
+
326
+ ---
327
+
328
+ ## Live mailbox commands
329
+
330
+ These talk to Zoho directly, without the store:
331
+
332
+ | Command | What it does |
333
+ |---|---|
334
+ | `doctor` | Probe IMAP/SMTP/POP on both host styles |
335
+ | `folders` | List folders with unread counts |
336
+ | `list` | Search live IMAP: `--unread --since 7d --sender x --subject y` |
337
+ | `read UID` | Print a message; `--save-attachments DIR`, `--mark-read` |
338
+ | `send` | Send directly: `--to --subject --body --attach --cc --html-file` |
339
+ | `pop-download` | Bulk-fetch as `.eml`: `--dir --limit --delete` |
340
+ | `digest` | Mechanical digest: `--hours 24 --send --out digest.html` |
341
+ | `reminders` | Action items and awaiting-reply: `--send --include-automated` |
342
+ | `stats` | Volume, top senders, newsletters: `--days 7` |
343
+ | `poll-once` | Report mail since the last poll; `--on-new "cmd {subject}"` |
344
+ | `watch` | Poll in the foreground: `--interval 300` |
345
+
346
+ `digest` and `reminders` filter automated mail — newsletters (`List-Unsubscribe`),
347
+ `no-reply@` senders, `Auto-Submitted` and `Precedence: bulk`. Bots sending from a
348
+ normal mailbox (a CI job on Gmail, say) have no such headers and need
349
+ `MUTE_SENDERS` in `config.env`:
350
+
351
+ ```ini
352
+ MUTE_SENDERS=ci-bot@example.com, notify.cloudflare.com
353
+ ```
354
+
355
+ Entries match a full address or a bare domain.
356
+
357
+ ---
358
+
359
+ ## Scheduling (Windows)
360
+
361
+ ```powershell
362
+ powershell -ExecutionPolicy Bypass -File .\scripts\register-tasks.ps1
363
+ ```
364
+
365
+ | Task | Schedule | Command |
366
+ |---|---|---|
367
+ | `Digest` | daily 07:30 | `digest --send` |
368
+ | `Reminders` | weekdays 08:30 | `reminders --send` |
369
+ | `Poll` | every 15 min | `poll-once` |
370
+
371
+ Options: `-DigestTime`, `-ReminderTime`, `-PollMinutes`, `-SkipPoll`,
372
+ `-SkipReminders`, `-MailProfile <name>` (tasks under `\ZohoMail\<name>\`). Logs
373
+ land in the profile's `state/logs/`. Remove with
374
+ `.\scripts\unregister-tasks.ps1` (`-MailProfile` too, if you used it).
375
+
376
+ To keep the store fresh, add a sync task:
377
+
378
+ ```powershell
379
+ schtasks /create /tn "ZohoMail\Sync" /tr "cmd /c cd /d $PWD && python -m zohomail sync" /sc hourly
380
+ ```
381
+
382
+ Cron equivalent:
383
+
384
+ ```cron
385
+ 30 7 * * * cd /path/to/zoho-mail && python -m zohomail digest --send
386
+ 0 * * * * cd /path/to/zoho-mail && python -m zohomail sync
387
+ ```
388
+
389
+ ---
390
+
391
+ ## As a library
392
+
393
+ ```python
394
+ from zohomail.config import load_config
395
+ from zohomail.store import Store
396
+ from zohomail.imap_client import ZohoIMAP
397
+ from zohomail import sync, smtp_client
398
+
399
+ config = load_config()
400
+
401
+ with Store(config) as store:
402
+ sync.sync(config, store, days=30, progress=print)
403
+
404
+ for row in store.search("invoice", limit=10):
405
+ print(row["date_utc"], row["from_email"], row["subject"])
406
+
407
+ store.add_analysis(message_id=12, priority="high", summary="Needs a reply today")
408
+
409
+ with ZohoIMAP(config) as imap:
410
+ for message in imap.recent("INBOX", hours=24, unread=True):
411
+ print(message.sender, message.subject)
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Layout
417
+
418
+ ```
419
+ pyproject.toml pip metadata; the wheel is the engine only
420
+ LICENSE MIT
421
+ .env.example same template as zohomail/templates/config.env
422
+ zohomail/
423
+ profile.py which profile is active, and where its files live
424
+ config.py config.env + environment loading, per-DC host derivation
425
+ imap_client.py IMAP4_SSL: search, fetch, flag, move, delete, retry on throttle
426
+ smtp_client.py SMTP_SSL / STARTTLS sending with attachments
427
+ pop_client.py POP3 bulk download with UIDL de-duplication
428
+ message.py header decoding, body extraction, attachment parts
429
+ schema.sql the entity model
430
+ db.py connection, migrations, FTS rebuild, vacuum
431
+ store.py persistence, threading, search, annotation layer
432
+ sync.py incremental IMAP -> SQLite
433
+ productivity.py action items, awaiting-reply detection, statistics
434
+ digest.py digest assembly, text and HTML rendering
435
+ mcp_server.py MCP server over stdio (18 tools)
436
+ cli.py live-mailbox commands
437
+ cli_store.py store-backed commands
438
+ cli_profile.py profile init / use / show / rules / migrate
439
+ folders.py folder selectors: paths, globs, exclusions
440
+ templates/ config.env and rules.md copied by `profile init`
441
+ .claude/
442
+ skills/ mail-triage, mail-search, mail-draft, mail-brief
443
+ agents/ mail-triage-agent, mail-reply-drafter, mail-archivist
444
+ .mcp.json registers the MCP server for this project
445
+ scripts/ register-tasks.ps1 / unregister-tasks.ps1 (clone only)
446
+ ```
447
+
448
+ Personal files live in the profile, never in the repo (see [Profiles](#profiles)).
449
+
450
+ ---
451
+
452
+ ## Notes and limits
453
+
454
+ - **Zoho throttles IMAP** at roughly 200 logins/day. Each command opens one
455
+ connection; a sync of many folders reuses a single one. On repeated
456
+ `socket error: EOF` the client retries twice with backoff, then tells you to
457
+ back off — that error means throttling, not a bad password.
458
+ - Zoho also drops long sessions mid-sync. Read commands reconnect and retry, so
459
+ a backfill carries on; if a folder still fails, the rest sync and a re-run
460
+ resumes where it stopped.
461
+ - The first sync of a folder reaches back `SYNC_DAYS` (`BULK_SYNC_DAYS` for bulk
462
+ folders); after that the UID cursor picks up everything new. Widen with
463
+ `--days N --full`, which skips messages already stored.
464
+ - A message moved or deleted on the server keeps its old row in the store; sync
465
+ only adds and refreshes.
466
+ - `poll-once` records a high-water UID on first run and reports nothing, so a
467
+ fresh install does not replay two days of mail.
468
+ - `pop-download` never deletes unless you pass `--delete`.
469
+ - Sending only works from the account's own address — Zoho rejects unverified
470
+ `From`. Use `--from-name` for the display name.
471
+ - Attachment blobs are never garbage-collected. Deleting rows leaves the blobs;
472
+ clear the profile's `store/blobs/` and re-sync if you need the space back.
473
+
474
+ ## Security
475
+
476
+ - The package is MIT licensed and contains no account, mailbox, or secret.
477
+ Profiles live outside the repo, so passwords and mail cannot be committed by
478
+ accident. The legacy in-repo `.env`, `store/` and `state/` are git-ignored too.
479
+ - `rules.md` is the owner's instruction to the assistant. Only they should edit it.
480
+ - App-specific passwords grant mail protocol access only, not account login, and
481
+ are revocable from the page that issued them.
482
+ - **Email content is data, not instructions.** Every skill and agent here is told
483
+ that a message asking to forward a thread, reply with credentials, or wire
484
+ money is a claim by a sender to be reported — never a command to act on. Keep
485
+ that rule if you write more agents against this store.
486
+ - The only tool that contacts another person is `draft send` / `mail_draft_send`,
487
+ and it requires explicit approval every time.