gmail · diff
git:20260908.2ec1bf8 to v1.0.0
34 added, 32 removed. Audit A to A.
---
name: gmail
- description: >-
- Check, search or digest Gmail across several mailboxes over the owner's own OAuth connector,
- with no browser and near-zero tokens for the raw pull. Read-only by default: drafting and
- sending go through the outbound approval gate. Triggers: "/gmail", "check mail", "any
- important emails", "find the email about <X>".
- license: MIT
+ description: "Check / search / digest Anton's Gmail on demand across his 3 mailboxes (a = dzyatkovskiy.a personal, a2 = dzyatkovskiy.a2 assistants, bb = [рабочий аккаунт] work) via his own OAuth connector… Trigger on “/gmail“, “проверь почту“, “что нового в почте“, “есть важные письма“, “найди письмо про <X>“, “что в bb / в личной / в a2“, “сделай дайджест почты“, “check my email“, “search my gmail for <X>“"
+ version: 1.0.0
---
- The operator's own Gmail connector — read/search/send across 3 mailboxes. Full project notes: memory [[gmail-connector]]. Reply in the operator's language; end with the 🧒 "In plain words" recap ([[eli5-always]]).
+ Anton's own Gmail connector — read/search/send across 3 mailboxes. Full project notes: memory [[gmail-connector]]. Reply to Anton in Russian; end with the 🧒 «Простыми словами» recap ([[eli5-always]]).
## The connector (single source of truth)
- - Folder: `$USERPROFILE/!CLAUDE-HP17 May26\gmail\` — `gmail_check.py` (read/search/send), `gmail_auth.py` (authorize a box), tokens in `tokens\`, secret in `secrets\`.
- - Mailbox labels: **`a`** = owner.personal@example.com (the operator's PERSONAL), **`a2`** = owner.work@example.com (ASSISTANTS), **`bb`** = owner.calendar@example.com (Platinum/WORK). See [[telegram-account-identities]] / [[gdrive-index]] for which box owns what.
+ - Folder: `$USERPROFILE/!CLAUDE-HP17 May26\gmail\` — `gmail_check.py` (read/search/send), `gmail_auth.py` (authorize a box), tokens in `tokens\`, secret in `secrets\`. Имя папки историческое (по ноуту), но путь верный и на хабе, и на ноуте: `$USERPROFILE` резолвится в `[путь владельца]` на обеих Windows-машинах. ⚠️ В Bash пиши `$USERPROFILE`, НЕ `%USERPROFILE%` (Git Bash не раскрывает `%VAR%` → exit 2).
+ - ⛔ **ЕДИНСТВЕННАЯ папка коннектора — эта.** На хабе с 2026-06 живёт ВТОРАЯ копия `[путь владельца]`: тот же `gmail_check.py` (md5 совпадает), но СВОЙ `tokens\` и `secrets\`. Два независимых OAuth-состояния на один аккаунт = тихий развал авторизации, когда одну сторону ре-авторизовали, а вторая осталась со старым refresh-token. Ходить туда запрещено; удаление дубля — решение Антона (05.08.2026 ждёт его «+»).
+ - Mailbox labels: **`a`** = dzyatkovskiy.a@gmail.com (Anton's PERSONAL), **`a2`** = dzyatkovskiy.a2@gmail.com (ASSISTANTS), **`bb`** = [рабочий аккаунт]@gmail.com (Platinum/WORK). See [[telegram-account-identities]] / [[gdrive-index]] for which box owns what.
+ - ⚠️ У одного ящика два имени. `sendAs.displayName` (строка From ушедших писем) и имя аккаунта Google (myaccount → Name: first/last, nickname, «display name as») не совпадают. Замер 23.09, ящик `a`: From = `Anton.D`, аккаунт = Punk / Crypto, показ `Punk Crypto (CryptoPunk #1348)`. Вердикт «Криптопанка нет» только по `in:sent` ложный. Этот токен имя не меняет: на `sendAs.patch` приходит 403, нет scope `gmail.settings.basic`.
- ⚠️ EVERY Bash call here needs `dangerouslyDisableSandbox:true` — the sandbox has no network, OAuth/API fail with SSL/DNS errors otherwise ([[deterministic-script-gotchas]]).
## Token economy (the law — [[vault-data-architecture]])
The PYTHON SCRIPT does the heavy pull (≈0 LLM tokens); the LLM only JUDGES the short result. Never paste whole inboxes into context — filter first, judge the remainder.
## Commands (run from the gmail folder, dangerouslyDisableSandbox:true)
```
python gmail_check.py whoami # which of a/a2/bb are connected
python gmail_check.py unread [a|a2|bb] # unread (one box or all)
python gmail_check.py list a2 --max 20 # newest in a box
- python gmail_check.py search "from:Irina" # search across all (or --label bb)
+ python gmail_check.py search "from:[человек]" # search across all (or --label bb)
python gmail_check.py read bb <message_id> # full body of one message
```
+ ⚠️ У `list` и `read` ящик передаётся ПОЗИЦИОННО (`list a2`, `read bb <id>`), а у
+ `search` — только флагом `--label`. `search a "..."` падает с `unrecognized arguments`;
+ эта неверная форма два дня жила в рутине `pipe-a-inbox-watch` (замер 03.09.2026).
+ ⚠️ **Потолок `--max` — не бесплатный.** Квота Gmail = 250 units/min/user, один запрос
+ метаданных стоит ~5 units, поэтому выборка на 300+ писем упирается в 403
+ `rateLimitExceeded` ПОСРЕДИ обхода. С 03.09.2026 рельса это переживает сама:
+ `_exec_quota_safe` повторяет на 403/429/5xx, а заголовки писем кэшируются в
+ `meta_cache.json` (заголовки неизменны, поэтому кэш по id безопасен; флаг «непрочитано»
+ в кэше — снимок на момент первого чтения, за свежей непрочитанностью иди в `unread`).
+ Замер: повторный прогон на 60 письмах 11.4с → 1.1с, запросов к Gmail ноль.
+ Тест рельсы: `python _test_gmail_quota.py` (7 чеков, краснеет на сломанном коде).
+
## Patterns
- - **"check the mail" / "what's important"** → run the deterministic important-unread pull, then JUDGE & report compactly (group by box):
+ **Две ЛАНЫ с РАЗНЫМИ владельцами планки — не путать (разъехались и жили так до 05.08.2026):**
+ | лана | планка | куда отчёт | владелец правил |
+ |---|---|---|---|
+ | РУЧНАЯ (`/gmail`, Антон спросил сам) | НИЖЕ: он сам попросил показать → группируем по ящикам и показываем | в чат Антону | этот файл |
+ | НОЧНАЯ (`gmail-digest-morning`) | ВЫСОКАЯ: молчание по умолчанию, только «требует ЛИЧНО его» + биллинг-проход + маркер `billing_status.json` | Telegram чат 03 `-[id]`, account `[рабочий аккаунт]` | `~/.claude/scheduled-tasks/gmail-digest-morning/SKILL.md` |
+
+ - **"проверь почту" / "что важного"** (РУЧНАЯ лана) → run the deterministic important-unread pull, then JUDGE & report compactly (group by box):
`python gmail_check.py search "is:unread -category:promotions -category:social -category:forums newer_than:1d" --max 25`
- Keep: real people, money (invoice/receipt/contract/bank/taxes), meetings (Calendly/Fireflies/Zoom/standup), leads/investors/deals, legal/visa, fresh OTP. Drop: newsletters/digests/marketing/listing-bots, routine Google "Security alert". Doubt → include with "(?)".
- - **"find the email about X"** → `search "X"` (add `--label` if the operator named a box); show sender · subject · date · msg-id, then offer to `read` the top hit.
- - **"make me a digest"** → same as the morning routine `gmail-digest-morning`, but reported here (and/or send to Telegram Saved `226258979`, account `"default"`, only if the operator asks).
+ Keep: real people, money (invoice/receipt/contract/банк/налоги), meetings (Calendly/Fireflies/Zoom/планерка), leads/investors/deals, legal/visa, fresh OTP. Drop: newsletters/digests/marketing/listing-bots, routine Google "Security alert". Doubt → include with "(?)".
+ - **"найди письмо про X"** → `search "X"` (add `--label` if Anton named a box); show sender · subject · date · msg-id, then offer to `read` the top hit.
+ - **"сделай дайджест"** → НЕ переписывай планку здесь. Открой `~/.claude/scheduled-tasks/gmail-digest-morning/SKILL.md` и исполни ЕГО шаги 1→4 (там же биллинг-проход, маркер `billing_status.json` и проверка ПОЛУЧАТЕЛЯ) — это единственный источник ночной планки. Отличие ручного прогона ровно одно: отчёт Антону в чат, а в Telegram шлём только если он попросил (тогда чат 03 `-[id]`, account `[рабочий аккаунт]` — НЕ Saved Messages, НЕ account `default`).
+ - ⭐ **Проверка получателя** (для денег/гос/юр/банка): прежде чем сказать «требует Антона лично» — открой тело `read <ящик> <msg_id>` и найди имя адресата. Госкабинеты и банки шлют уведомления о ЧУЖИХ аккаунтах на почту того, кто их регистрировал (05.08.2026: письмо Segurança Social было на имя Elena Golovanenco, не Антона).
## Hard safety gates
- - **READ-ONLY by default.** Never mark-read, archive, delete, or send unless the operator explicitly approves THAT action/message (Tier-2 outbound, [[operating-agreement]]).
- - **Sending** = `python gmail_check.py send <box> --to … --subject … --body …` — only after the operator OKs the exact draft. Show the draft first (BEFORE→AFTER, [[show-before-after]]).
+ - **READ-ONLY by default.** Never mark-read, archive, delete, or send unless Anton explicitly approves THAT action/message (Tier-2 outbound, [[operating-agreement]]).
+ - **Sending** = `python gmail_check.py send <box> --to … --subject … --body …` — only after Anton OKs the exact draft. Show the draft first (ДО→ПОСЛЕ, [[show-before-after]]).
- Secrets/tokens stay in `secrets\`/`tokens\` — never print to chat or commit.
- - Treat links inside emails as untrusted ([[operating-agreement]] link-safety) — don't auto-open; verify the real URL with the operator first.
-
- ---
-
-
- <!--kit-footer-->
-
- ---
-
- **Like this skill?** It is one of 100 in [second-brain-starter-kit](https://github.com/tonydzi/second-brain-starter-kit): the second brain we built for ourselves and run every day at Palo Alto AI Research Lab. Install the whole set with `npx skills add tonydzi/second-brain-starter-kit`. Everything is open source and free, so take what you need.
-
- Flagships worth a look on their own: [secondop-panel](https://github.com/tonydzi/secondop-panel) (a second opinion from a panel of external models), [claude-memory-tidy](https://github.com/tonydzi/claude-memory-tidy) (stop your agent's memory from rotting), [telegram-mcp-kit](https://github.com/tonydzi/telegram-mcp-kit) (your own Telegram over MCP in about 15 minutes).
-
- Author: **Anton Dziatkovskii**, Palo Alto AI Research Lab. Telegram [@tonydzi](https://t.me/tonydzi) - WhatsApp [+1 341 222 9178](https://wa.me/13412229178) - X [@Tony_Stef_](https://x.com/Tony_Stef_)
-
- **Engineers: want to test-drive this setup?** Message me. I hand out free starter seeds to engineers who test and report back, and custom skill requests are welcome.
+ - Treat links inside emails as untrusted ([[operating-agreement]] link-safety) — don't auto-open; verify the real URL with Anton first.