Gmail-to-Google-Sheets job application tracker. When run, it prompts you for the number of most recent inbox emails that are job applications, parses them for company/position/date, and appends the results to a Google Sheet.
- Prompts for how many recent inbox emails to process
- Extracts company name and position using AI (OpenRouter API)
- Writes to Google Sheets: Position | Company | Date Applied
- Moves processed emails to trash
- Deduplicates using SQLite to avoid duplicate entries
- Scheduled via cron
- Python 3.14+
- uv package manager
- Google account with Gmail
- Google Cloud project (free tier works)
- OpenRouter API key (for AI extraction)
git clone <repo-url>
cd job-tracker
uv sync- Go to Google Cloud Console
- Click the project dropdown at the top and select New Project
- Name it something like "Job Tracker" and click Create
- Make sure your new project is selected in the dropdown
- Go to APIs & Services > Library
- Search for Gmail API, click it, and click Enable
- Go back to the Library
- Search for Google Sheets API, click it, and click Enable
- Go to APIs & Services > OAuth consent screen
- Select External and click Create
- Fill in required fields (app name, support email, developer email)
- Click Save and Continue through the remaining steps
- On the Test users page, add your Gmail address
Your app will be in "Testing" mode. Only test users can authenticate. This is fine for personal use.
- Go to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Select Desktop app
- Click Create, then Download JSON
- Save the file as
config/credentials.json
cp config/config.yaml.example config/config.yamlEdit config/config.yaml:
# Get this from your Google Sheet URL:
# https://docs.google.com/spreadsheets/d/THIS_IS_YOUR_SPREADSHEET_ID/edit
spreadsheet_id: "your-spreadsheet-id-here"
# The sheet/tab name within the spreadsheet
sheet_name: "Sheet1"
# OpenRouter API key (https://openrouter.ai/)
openrouter_api_key: "your-openrouter-api-key-here"Create a new Google Sheet and copy its ID from the URL.
The first run will open your browser to authenticate with Google:
.venv/bin/python -m app.mainYou'll be prompted to sign in and grant permissions for Gmail and Sheets. After successful auth, tokens are saved locally and you won't need to authenticate again unless they expire.
If you get Error 403: access_denied, make sure your email is added as a test user in the OAuth consent screen.
crontab -eAdd these lines (replace with your actual path):
0 12 * * * /path/to/job-tracker/scripts/run.sh
0 22 * * * /path/to/job-tracker/scripts/run.sh
A terminal window will pop up at 12pm and 10pm daily asking how many recent emails to process. Enter the number, and it runs. Press Enter to close the window when done.
.venv/bin/python -m app.mainThe program will ask:
How many of your most recent inbox emails are job applications?
Enter a number. It fetches that many of your most recent inbox emails, parses each one for company/position/date, appends the results to your Google Sheet, and trashes the processed emails.
- Prompt: Asks the user how many recent inbox emails are job applications
- Fetch: Retrieves that many most recent emails from the Gmail inbox
- Parse: Uses regex and OpenRouter AI (Gemini 2.0 Flash) to extract company, position, and date
- Deduplicate: Checks SQLite database to skip already-processed emails
- Write: Appends rows to Google Sheet (Position | Company | Date Applied)
- Cleanup: Moves processed emails to Gmail trash
job-tracker/
├── app/
│ ├── main.py # Pipeline orchestration
│ ├── gmail_client.py # Gmail API client
│ ├── parser.py # Email parsing + AI extraction
│ ├── sheets.py # Google Sheets client
│ ├── dedupe.py # SQLite deduplication
│ ├── models.py # Data models
│ └── config.py # Configuration loading
├── config/
│ ├── config.yaml # All settings + API key (git-ignored)
│ ├── config.yaml.example
│ ├── credentials.json # OAuth client (git-ignored)
│ ├── token.json # Gmail token (git-ignored)
│ └── sheets_token.json # Sheets token (git-ignored)
├── data/
│ └── processed.sqlite # Processed email IDs (git-ignored)
├── logs/
│ ├── app.log # Application logs
│ └── cron.log # Cron execution logs
├── scripts/
│ └── run.sh # Cron wrapper script
├── pyproject.toml
└── README.md
Enable the Gmail API in Google Cloud Console: https://console.developers.google.com/apis/api/gmail.googleapis.com
Add your email as a test user in APIs & Services > OAuth consent screen.
Enable the Sheets API in Google Cloud Console: https://console.developers.google.com/apis/api/sheets.googleapis.com
Delete the token files and re-run to re-authenticate:
rm config/token.json config/sheets_token.json
.venv/bin/python -m app.main# Clear processed emails database
rm data/processed.sqlite
# Clear logs
rm logs/*.log- All secrets (API keys, OAuth tokens) live in
config/which is git-ignored - The Gmail scope allows read/modify (needed to trash emails)
- No email content is permanently stored
MIT