gmail · diff
v1.0.0 to v1.0.0
2 added, 2 removed. Audit A to A.
---
name: gmail
- 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>“"
+ description: "Check / search / digest Anton's Gmail on demand across his 3 mailboxes (a = youraccount.a personal, a2 = youraccount.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
---
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\`. Имя папки историческое (по ноуту), но путь верный и на хабе, и на ноуте: `$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.
+ - Mailbox labels: **`a`** = you@example.com (Anton's PERSONAL), **`a2`** = you.assistants@example.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:[человек]" # 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
**Две ЛАНЫ с РАЗНЫМИ владельцами планки — не путать (разъехались и жили так до 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/банк/налоги), 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 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 Anton first.