2026-08-04 Gmail Recruiter Filter Audit and Fix
What I set out to do
Check how well the Gmail Inbox Filters recruiter filters were performing a month after the 2026-07-04 false-positive cleanup, then fix whatever the audit surfaced.
What I actually did
- Audited both directions via the Gmail MCP. Precision: 13 of 28 threads labeled since 7/04 were real recruiters; ~15 false positives had re-accumulated (OpenTable worst at 8 threads, plus Resy, Air Canada, OpenAI, Substack, python.org, one GitHub notification message). Recall: 6 recruiter senders sat in the inbox unlabeled (DoorDash, Pendo, Lumos, Evolve, Kyth, and the first Acquire Me message).
- Found the root cause of the misses: the 26-domain allow-list filter from June had silently vanished; only the phrase filter remained.
- Cleaned all labels via the MCP (15 unlabels, 6 labels applied).
- Used claude-in-chrome to drive the Gmail Settings UI directly: tightened the phrase filter (dropped the “reach out” phrases and “your experience at”, added seven transactional sender exclusions) and recreated the allow-list filter with the dotted alias plus 15 observed recruiter domains.
- Final unread-inbox sweep against all filters: only recruiter mail remained (the 6 threads labeled earlier plus a second unlabeled amya.agency thread from 7/16). Filed all 7 as the filters would (labeled + archived, still unread). Everything else was clean.
- Mailing-list pruning: prepared unsubscribe drafts for Spark-Dev, Numpy-Discussions, Scikit-Learn, PyData, and PyStatsModel (kept Core-Mentorship and Python-Dev), deleted the 5 corresponding filters, kept the labels for the archive.
- Follow-up fix: patient-portal 2FA/validation codes were being archived by the Health filters. Both practices run on AdvancedMD, so all codes came from
noreply@advancedmd.comand filed under Harmony-Bay. Added a code-phrase Doesn’t have exclusion to both the advancedmd and vsee/phreesia/lifestance filters so codes stay in the inbox.
What was striking
- The missing allow-list filter reframed everything: the standing “fix the dotless to:” next action was chasing the wrong cause, and would have matched all mail anyway since the dotless address is the primary. Killed it as a design error.
list:*in Gmail’s Doesn’t have field is unreliable — it visibly failed to exclude mailing-list and Substack mail. Explicitfrom:exclusions are the dependable form.- OpenTable’s review requests (“your feedback about your experience at:”) were a perfect collision with the recruiter phrase “your experience at”. Phrase filters against transactional boilerplate need sender exclusions, not just better phrasing.
- Filter edits turned out to be fully automatable through the browser; the “filters are manual-only” limitation in the project note was stale.
gmailctl adoption (later session)
Researched filters-as-code tooling and landed on gmailctl: Jsonnet config, diff/apply via the Gmail API, download to import existing filters. Added it to the dotfiles as nix/home-manager/modules/gmailctl.nix (commit d04c380), then drove the entire GCP enablement in Chrome: project gmailctl-504600, Gmail API enabled, OAuth consent screen, Desktop client, credentials.json into ~/.gmailctl/, app pushed to Production, OAuth grant completed. gmailctl download seeded nix/home-manager/files/gmailctl/config.jsonnet (16 labels, 11 rules), now deployed by home-manager as ~/.gmailctl/config.jsonnet (commit 610b7b9); gmailctl diff confirms repo and live Gmail match exactly.
Also discovered Google Groups can be left directly from the web UI (group page, membership, Leave group): left PyData and pystatsmodels that way, making their unsubscribe drafts obsolete. The spark/numpy/scikit-learn drafts still need sending. Surfaced two forgotten memberships: the python-dev googlegroups mirror and SciPy-user. See Gmail Inbox Filters.
Evening: remaining unsubscribes, Jsonnet refactor, NJ Transit fix
Left SciPy-user via the Groups web UI and completed the Spark unsubscribe end-to-end (sent the draft, replied to ezmlm’s confirmation). Refactored the gmailctl config into named Jsonnet arrays (44e5ddf), verified byte-identical with gmailctl diff -f before deploying.
Then the new setup paid for itself immediately: NJ Transit MyTix signup (made with the dotted alias) had 4 messages recruiter-labeled and archived, including two verification codes. The alias clause in the allow-list filter was the culprit; messages to the dotless address were untouched, which isolated it. Restored the 4 threads, added recruiterAliasExclusions (from:njtransit.com) plus the verification-code carve-out to the allow-list rule, and shipped it with the first real gmailctl apply (bfb73d7). The “dotted alias = recruiters” assumption now has a documented escape hatch for alias-registered services.
Late: Uber receipts filter
Surveyed the inbox to recommend a next round of filters; the standout was Uber’s 2-emails-per-trip receipt duplication. Header inspection (“Show original”) showed Iterable transactional mail with no List-Unsubscribe/List-ID, so the filter keys on from:noreply@uber.com + subject "trip with Uber". Added via gmailctl with a new general-purpose Receipts label (d82c462), applied, and bulk-filed ~200 historical trip threads back to 2022 through the Gmail search UI. See Gmail Inbox Filters.
2026-08-05: wrap-up round
Sent the numpy/scikit-learn unsubscribe emails (Mailman confirmations pending on the user), trashed the superseded XML filter exports, and added 3 more filters via gmailctl (ParkMobile chatter with a magic-link carve-out, Resy/OpenTable feedback surveys, Amex Send echoes), backfilling 52 inbox threads through Gmail’s bulk UI. Capped it with a daily launchd drift check (gmailctl-drift-check) that diffs live filters against the repo config and alerts through the athena ntfy topic using the SigNoz webhook credentials; test publish confirmed on the phone. The original vanished-filter failure mode is now both structurally prevented (git as source of truth) and actively monitored. See Gmail Inbox Filters.