jobtrack
A local job-search workspace built from the recruiting mail already in your Gmail. Find applications, assessments, interviews, offers, and rejections; follow each email timeline; and keep your next steps beside every opportunity.
Live demo: the real web UI with fictional
applications from scripts/seed_demo.py. It is read-only and contains no data
from a real inbox. Build it yourself with python scripts/build_demo.py site.
The app runs on your own machine with Python, FastAPI, and SQLite. It asks Gmail
for one permission — gmail.readonly — so it can never send, delete, or
modify mail. Classification works from offline rules and is optionally assisted
by a local or hosted model.
| Reads recruiting mail you already received | Cannot send, delete, or modify mail |
| Stores everything in a local SQLite file | Cannot reach your inbox without your approval |
Runs on 127.0.0.1 with no external service |
Has no shared server or account; you can self-host your own copy |
If you configure a hosted model provider, the email being classified is sent to
that provider for the length of the request. Rules-only mode (--llm-mode off)
and local models keep that content on your device. See
SECURITY.md for what lands on disk.
Install
Python 3.10 or newer. From PyPI:
pipx install jobtrack # or: uv tool install jobtrack
Or from a clone:
uv venv
uv pip install -e "."
source .venv/bin/activate
Or with plain pip:
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
Either way you get a jobtrack command. Check it with jobtrack --help.
Authorize Gmail
There is no shared OAuth client here, and that is deliberate: your token goes to your machine, not to someone else's server. Google requires you to create your own client, which is the one part of setup that is not a single command.
- Open Google Cloud Console and create a project (or pick an existing one).
- Enable the Gmail API for that project.
- Under Google Auth Platform → Branding, set an app name and support email. Choose External.
- If the app is in Testing, add your own address under Test users. Skip this if you publish the app.
- Under Data Access, add exactly one scope:
https://www.googleapis.com/auth/gmail.readonly - Create an OAuth client ID of type Desktop app and download the JSON.
- Authorize:
jobtrack auth --credentials ~/Downloads/client_secret.json
A browser opens, you approve the read-only permission, and a token is cached at
token.json (mode 600).
If your token stops working after about seven days: your OAuth app is in Testing, where Google expires refresh tokens quickly. This is not a bug. Either publish the app (Google Auth Platform → Publish app) or re-run
jobtrack auth. It is the single most common problem people hit.
First sync
jobtrack serve
Then open http://127.0.0.1:8765.
Look before you import: open Sync options, set a lookback of 90 days and a limit of 50, and choose Preview first. That lists candidate messages and their classifications without changing anything. When it looks right, choose Sync Gmail. Your token and database are reused on every later start.
The layout is deliberately dense: charcoal surfaces, amber controls, monospace
typography, compact top navigation, packed panels. The header toggle switches to
a warm light theme, and the layout adapts to narrow screens without a separate
mobile app. Press / to jump to application search.
- Overview: current stages, active opportunities, interview count, response rate, an eight-week activity chart, role categories, follow-ups, and recent mail.
- Applications: searchable list and board views; stage filters; CSV export; historical snapshots.
- Activity: recent recruiting messages and persistent sync history.
- Sync options: lookback, date bounds, message limit, custom Gmail search, whether to track mail you sent, and reclassification.
- Settings: provider connections, masked API keys, live model discovery, and a multi-model review team.
One application, end to end
Every card is backed by the emails that produced it, and the status is derived from those events rather than typed in by hand — so "did they ever reply, or did I just wait?" is answerable from one panel.
Each event shows its sender, arrival time, the snippet that earned its label, a link into the original Gmail conversation, and how it was classified. Correct it when the mail disagrees; the correction sticks across later syncs.
Command line
jobtrack sync --since 90 --limit 100 # import the last 90 days
jobtrack sync --track-sent --dry-run # preview your own applications
jobtrack list --active # what is still open
jobtrack show 7 # one application and its timeline
jobtrack stats # stage counts and response rate
jobtrack check acme # have I applied here before?
jobtrack add "Acme" --role "SRE" --date 2026-10-01 # an application that sent no email
jobtrack digest --days 7 # what changed, and what's coming up
jobtrack report --anonymize -o retro.md # Markdown retrospective
jobtrack details # read interview times from older mail
jobtrack export --format csv -o applications.csv
jobtrack serve --port 9000 --no-browser
jobtrack schedule install --at 08:00 # daily import via launchd or cron
jobtrack logout
--since accepts a number of days, 7d, 3m, 2y, or a calendar date.
--as-of replays stored history without refetching Gmail. Global --db PATH
selects another database and --json renders machine-readable output.
Read more
- Architecture — how a message becomes an application, and the database invariants
- Model analysis — providers, the four modes, and multi-model consensus
- Metrics and corrections — response rate, attention cues, historical replay, syncing
- HTTP API — every endpoint
- SECURITY.md — the Gmail scope, on-disk files, and how to report an issue
Code map
- sync.py handles import ordering, duplicate detection, preview mode, and classification handoff.
- classify.py contains the offline relevance and lifecycle rules.
- store.py persists applications and their email-event history.
- insights.py builds the Insights page, digest, lookup, contacts, and follow-up drafts; details.py reads interview times, deadlines, and meeting links.
- web.py exposes the local workspace; the web tests exercise timelines and API behavior.
- Classifier tests use explicit email examples to check event labels and irrelevant-mail rejection.
The synthetic screenshots and rules-only mode provide a way to explore the project without sending email content to a model provider.
Development
uv pip install -e ".[dev]"
pytest -q
ruff check --select E4,E7,E9,F,I jobtrack tests
CI runs the same two commands on macOS and Linux across Python 3.10 and 3.12, and verifies the package builds.
Data lives in ~/Library/Application Support/jobtrack on macOS,
~/.local/share/jobtrack on Linux, and %APPDATA%/jobtrack on Windows. Override
with JOBTRACK_HOME. Everything is git-ignored except .env.example.
The screenshots come from a seeded database of invented applications, so they carry no real personal data:
python scripts/seed_demo.py /tmp/jobtrack-demo.db
jobtrack --db /tmp/jobtrack-demo.db serve --port 8765
License
MIT. See LICENSE.
Metadata
Release files for jobtrack 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| jobtrack-0.1.0.tar.gz | 1.4 MB | With provenance | Details for jobtrack-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| jobtrack-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for jobtrack-0.1.0-py3-none-any.whl |
Total release size: 1.5 MB
Release files / jobtrack-0.1.0.tar.gz
| Download URL | jobtrack-0.1.0.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3e4dcdd6d0389b63c4960b29815fef1ec1bfab818b4772980292b7c4eb973540
|
|
BLAKE2b-256 checksum How to use checksums |
91cf66df35614f03cbfa073190567ed0928736df325a34eefc089ae7f2e6d243
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.
Transparency logRelease files / jobtrack-0.1.0-py3-none-any.whl
| Download URL | jobtrack-0.1.0-py3-none-any.whl |
|---|---|
| Size | 171.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4f7adf60a2c640c59604293be63c9d16f7d2f878005d1799fe4e3194b6cb7da4
|
|
BLAKE2b-256 checksum How to use checksums |
6af534b2e5c53077584120e95e26da95a9a5e6c09ab3138259ee4ec99b90cbbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.
Transparency logSecurity for jobtrack 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for jobtrack-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.