# How Gmail Sync Works for Auto-Detecting Application Status from Emails

> Discover how Gmail sync automatically detects your job application status like interviews or rejections directly from your inbox. Approve updates to your tracker with ease.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: internals
- Published: 2026-07-26

---

**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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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 `status` is not final)
- Common ATS domains like `greenhouse.io`, `lever.co`, and `myworkday.com`
- The `newer_than:30d` parameter (or custom date)
- `in:inbox` to 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/gmail_sync/state.json), the same email is never proposed twice.
- **Append-only tracking**: Both `job_search_tracker.csv` and [`outcome.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/outcome.md) files 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/gmail_sync/state.json), the tracker CSV, and `documents/applications/**` are listed in `.gitignore` to keep personal data out of version control.

## Example Usage and Output

Invoke the command with no arguments to scan the default 30-day window:

```text
> /gmail-sync

```

The system returns a proposal table similar to:

```markdown

## 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-sync` command 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.csv` or [`outcome.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/outcome.md).
- The system maintains idempotency through [`gmail_sync/state.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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.