Repository navigation
Connect Gmail
Run playbooks/gmail-triage/triage.yaml against your real inboxes, one or more Gmail accounts, read-only.
Needs noetl 5.3.0 or newer:
noetl credentialandgoogle_oauthauth arrived in 5.2.0 (noetl/cli#96), and the playbook'sthinking: disabledcost control in 5.3.0 (noetl/cli#97).
flowchart LR
A[Claude<br/>framework: claude] -->|list_unread account| L[tools/list_unread.yaml]
A -->|get_message account, id| G[tools/get_message.yaml]
L & G -->|"http, auth: google_oauth<br/>credential: gmail-<account>"| B[noetl bridge]
B -->|refresh token → access token| T[(oauth2.googleapis.com)]
B -->|Bearer access token| M[(Gmail API, read-only)]
S[~/.config/noetl/credentials/gmail-<account><br/>mode 600] -.-> B
- One credential per account, named
gmail-<account>(gmail-work,gmail-personal, …). It holds a refresh token for the scopegmail.readonly. - On each run, NoETL exchanges it for a one-hour access token, cached in-process. Neither token ever enters playbook variables or step results.
- Claude sees only what the two tools return: message ids, then From / Subject / Date / snippet. It never sees bodies.
You need one OAuth client, shared by all your accounts.
-
Go to console.cloud.google.com and create a project, e.g.
noetl-mail. -
Open APIs & Services → Library, search for Gmail API, and click Enable.
-
Open Google Auth Platform (formerly the "OAuth consent screen"):
-
Branding: an app name such as
NoETL mail triageand your email. - Audience: User type External. Under Test users, add each Gmail address you'll connect.
-
Data access: add the scope
.../auth/gmail.readonly.
-
Branding: an app name such as
-
Under Clients → Create client, choose application type Desktop app, then click Download JSON.
-
Save the file privately:
install -d -m 700 ~/.config/noetl && install -m 600 ~/Downloads/client_secret_*.json ~/.config/noetl/google-client.json
Testing vs. production. While the app's publishing status is Testing, Google expires its refresh tokens after 7 days, so you'd re-authorize weekly. For a long-running personal setup, set Audience → Publishing status → In production. The consent screen will then say Google hasn't verified the app; choose Advanced → Go to … (unsafe) for your own accounts. Unverified apps are limited to 100 users, which is fine for personal use. A Google Workspace account can use user type Internal instead, which avoids both the warning and the expiry.
noetl credential google gmail-work --client-secret-file ~/.config/noetl/google-client.json --login-hint you@work.exampleYour browser opens Google's consent page. Pick the account and allow read-only Gmail access. The terminal then confirms which mailbox it stored:
Stored google_oauth credential `gmail-work` for you@work.example at ~/.config/noetl/credentials/gmail-work (mode 600); scopes: https://www.googleapis.com/auth/gmail.readonly
Repeat for every account:
noetl credential google gmail-personal --client-secret-file ~/.config/noetl/google-client.json --login-hint you@gmail.comOn a machine without a browser, add --no-open and open the printed URL yourself. The redirect goes to 127.0.0.1, so finish on the same machine or forward the port.
Check what's stored (values are never shown):
noetl credential listnoetl credential set anthropicPaste the sk-ant-api… key at the hidden prompt. Once this is stored, no environment variable is needed, including under systemd.
The account names in accounts= are the part after gmail-:
noetl run playbooks/gmail-triage/triage.yaml --runtime local --set accounts=work,personal,family --verboseWith --verbose you'll see one list_unread call per account, then a batch of get_message calls, then the report. Without credentials for an account, that account's tool call fails, and Claude says so and carries on with the others.
Cost. Measured on four real inboxes (27 messages, 3 turns):
| setup | input | cache write / read | output | approx. per run |
|---|---|---|---|---|
| Sonnet, 3 days, all tabs, raw Gmail JSON | 87,715 | — | 6,148 | ~$0.35 |
Haiku, thinking: disabled, Primary tab, last day, compacted tool output, prompt caching (the playbook default) |
8 | 8,158 / 2,126 | 2,667 | ~2–3¢ |
What drives it: the number of messages (each get_message result goes back to the model), the size of each result (the compact rhai steps cut it to sender/subject/date/snippet), and the model. To trim further, lower max_results or narrow query in tools/list_unread.yaml. thinking: disabled and prompt caching need noetl 5.3.0; on older releases set --set model=claude-sonnet-5-5, because Haiku can spend the summary turn on reasoning and return nothing.
cp systemd/gmail-triage.* ~/.config/systemd/user/ && systemctl --user daemon-reload && systemctl --user enable --now gmail-triage.timerEdit --set accounts= in gmail-triage.service to your account names. Read the result with journalctl --user -u gmail-triage.service.
-
One account: remove the app at myaccount.google.com/permissions, then delete
~/.config/noetl/credentials/gmail-<account>. - Everything: delete the OAuth client in Google Cloud. All refresh tokens issued to it stop working.
Configuration Reference § http auth · Security · Troubleshooting
agent-playbooks
Code