Google Apps Script job tracker that reads emails from a Gmail label, extracts job status updates with OpenAI, and syncs results into a Google Sheet.
This project was built with assistance from OpenAI Codex.
- Reads threads from the Gmail label
Jobs/Inbound - Uses the first message id as a stable dedupe key (
thread_id) - Extracts
company,role_title, andstatusfrom email content using OpenAI - Falls back to heuristic parsing if the LLM call fails
- Inserts new rows and updates existing rows in the
Applicationstab - Supports status precedence to avoid overwriting some manual workflow statuses
- A Google account with Gmail and Google Sheets access
- An OpenAI API key
- A Google Sheet to use as the tracker
- Google Apps Script access
- Create a new Google Sheet.
- Rename the tab to
Applications. - Add this exact header row in row 1:
thread_id | company | role_title | status | applied_date | status_last_updated | Last Follow up | notes
Header names must match exactly, including capitalization and spacing.
- In Gmail, create a label named
Jobs/Inbound. - Add job related emails to this label manually or with Gmail filters.
- Open the sheet.
- Go to
Extensions->Apps Script. - Create or open
Code.gs. - Paste the full script code for this project.
- Save.
In Apps Script:
- Go to
Project Settings->Script properties. - Add:
OPENAI_API_KEY=sk-...OPENAI_MODEL=gpt-4.1-mini(optional, defaults to this value)
- In Apps Script, run
testOneJobEmailExtraction_()once. - Review logs in
View->Logsto confirm extraction works. - Run
syncJobEmails_Agentic()manually once. - Confirm rows appear in the
Applicationssheet.
On first run, Google will ask for permissions:
- Gmail read access
- Spreadsheet read/write access
- External request access (
UrlFetchApp) - Trigger management (if you create scheduled triggers)
Approve all required scopes.
- Run
createJobSyncTrigger_10min()once to create a time based trigger. - The sync will run every 10 minutes.
maxThreadsPerRunandllmDelayMsBetweenCallsshould be tuned to your OpenAI rate limits.status_last_updatedis used to skip threads when there is no newer message.thread_idstores the first message id and is the primary stable dedupe key.- Keep manual follow up notes in
Last Follow up.