How Gmail Sync Works for Auto-Detecting Application Status from Emails
The /gmail-sync command scans your Gmail inbox through the Claude Code Gmail connector to detect job application status changes—such as interview invites, offers, or rejections—and proposes updates to your job_search_tracker.csv only after explicit user approval.
The ai-job-search repository by MadsLorentzen automates job application tracking using Claude Code commands. Its Gmail sync auto-detecting application status feature operates in a strictly read-only, proposal-first mode to ensure your data remains accurate and under your complete control.
The Gmail Sync Workflow
The /gmail-sync command, defined in .claude/commands/gmail-sync.md, executes an 11-step pipeline to surface relevant emails and extract status signals. It begins by loading your current application state from job_search_tracker.csv and gmail_sync/state.json, then constructs a targeted Gmail query to fetch only new messages from open applications.
Prerequisites and Input Parsing
Before scanning, the command verifies the Gmail MCP connector is available. It accepts optional arguments such as <company> or since <date> to limit scope, defaulting to a 30-day look-back window if unspecified.
Building the Gmail Search Query
The system constructs a Gmail search string combining:
- An optional user-defined label for job-search mail
- Normalized company names from open applications (rows where
statusis not final) - Common ATS domains like
greenhouse.io,lever.co, andmyworkday.com - The
newer_than:30dparameter (or custom date) in:inboxto restrict results to received messages only
This query is passed to the Gmail API's search_threads endpoint.
Filtering and Classification
The command filters out any thread whose message IDs exist in processed_message_ids stored in gmail_sync/state.json. For remaining threads, it fetches full thread bodies via get_thread to avoid relying on truncated snippets.
Each message is matched to an open application by comparing normalized sender domains, display names, subjects, and bodies against known company names. The system detects signal types—assessment, interview invite, offer, rejection—using key phrases. It generates a proposed row containing the target company, role, detected signal, and new status. Conflicting signals (e.g., rejection after an offer) are flagged for manual handling rather than auto-proposed.
User Approval and Write Operations
No data is written during classification. Instead, the command presents a markdown table of proposals, plus sections for unmatched emails and conflicts. The user must reply "approve all" or specify which rows to skip.
Upon approval, the command updates job_search_tracker.csv with new status values and appends dated notes like 2026-07-12 gmail-sync: interview ("Schedule call"). It also updates documents/applications/<company>_<role>/outcome.md by ticking appropriate stage checkboxes and appending notes.
Finally, it updates gmail_sync/state.json with processed message IDs and the current timestamp, ensuring idempotence on subsequent runs. The command concludes with a staleness check, flagging any open application that has had no activity for 30 days.
Architectural Safeguards
The implementation prioritizes data integrity through several strict design principles:
- Read-only Gmail access: The command never modifies Gmail—no labeling, archiving, or deletion occurs.
- Idempotent processing: By persisting processed message IDs in
gmail_sync/state.json, the same email is never proposed twice. - Append-only tracking: Both
job_search_tracker.csvandoutcome.mdfiles are only appended to, preserving a reliable audit trail. - User-first approval: No write occurs before explicit consent, preventing silent corruption of application history.
- Git-ignored personal data:
gmail_sync/state.json, the tracker CSV, anddocuments/applications/**are listed in.gitignoreto keep personal data out of version control.
Example Usage and Output
Invoke the command with no arguments to scan the default 30-day window:
> /gmail-sync
The system returns a proposal table similar to:
## Gmail Sync - Proposed Updates - 2026-07-26
Scanned 12 threads (8 new messages) since newer_than:30d.
### Proposed Changes (reply "approve all", or list which to skip)
| # | Company | Role | Signal | Current → Proposed Status | Source Email (date) |
|---|---------|------------|-------------------|---------------------------|---------------------|
| 1 | Acme | Data Engineer | Interview invite | applied → interview | "Schedule interview" (2026-07-10) |
| 2 | BigCo | Backend Dev | Offer extended | interview → offer | "Offer letter" (2026-07-12) |
Replying approve all commits these changes to your tracker files.
Summary
- The
/gmail-synccommand scans Gmail using the Claude Code connector to detect job application status changes. - It constructs targeted queries using company names and ATS domains to find relevant emails.
- Classification relies on full thread bodies, not snippets, to identify signals like interviews or offers.
- All updates require explicit user approval before writing to
job_search_tracker.csvoroutcome.md. - The system maintains idempotency through
gmail_sync/state.json, ensuring emails are processed only once.
Frequently Asked Questions
Does Gmail sync modify or delete my emails?
No. The /gmail-sync command operates in read-only mode. It never labels, archives, moves, or deletes Gmail messages. It only reads thread content to propose status updates for your local tracker files.
How does the system avoid proposing the same email twice?
The command stores processed message IDs in gmail_sync/state.json. On subsequent runs, it filters out any thread whose ID already appears in this list, guaranteeing idempotent processing and preventing duplicate proposals.
What happens if the system detects conflicting signals?
Conflicting signals—such as a rejection email arriving after an offer—are not automatically proposed. Instead, they are flagged in the output for manual handling, ensuring that automated updates never overwrite important status changes without your review.
Where are the proposed updates stored before approval?
Proposed updates are displayed in the conversation output as a markdown table. They are not written to job_search_tracker.csv or documents/applications/<company>_<role>/outcome.md until you explicitly reply with "approve all" or select specific rows to apply.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →