How vesaias/jobnavigator works
1. JobNavigator is a self-hosted, three-part system: a Python backend API, a React/Tailwind frontend, and a Chrome extension for passive LinkedIn/job capture. PostgreSQL is the persistent store, and Docker Compose/Caddy/nginx are used to run and expose the stack locally or self-hosted.
2. The backend is the orchestration center. backend/main.py and the FastAPI routes under backend/api/ expose the domain surface for jobs, companies, searches, resumes, applications, cover letters, persona, and settings. These routes are the main boundary between the UI/extension and the core job automation logic.
3. Job discovery is split across multiple ingestion sources. The scraper subsystem handles career pages via Playwright, plus dedicated ATS adapters in backend/scraper/ats/ for Workday, Greenhouse, Lever, Ashby, Oracle HCM, Phenom, TalentBrew, Rippling, SmartRecruiters, Google, Meta, and generic/custom pages. Separate source adapters cover aggregators and special inputs like JobSpy, Levels.fyi, Jobright, LinkedIn personal collections, and the Chrome extension.
4. Scraping is not just fetching pages; it has a processing pipeline in backend/scraper/_shared/ and backend/scraper/orchestrator.py. Shared concerns include browser control, URL normalization, URL safety, deduplication, and filtering, which turn many heterogeneous sources into one normalized job record model before it reaches the database.
5. After ingestion, the backend enriches and filters jobs. The analyzer layer computes salary extraction, H-1B/company-labor lookup, CV scoring, cover-letter generation, and LLM-backed analysis. This is where job data becomes actionable: matching against resumes/persona, generating reports, and preparing tailored documents.
6. The LLM boundary is abstracted so multiple providers can be swapped without changing the rest of the system. backend/analyzer/llm_client.py plus cost/logging helpers support Anthropic/Claude, OpenAI, Ollama, and OpenAI-compatible providers, while tests indicate prompt caching and call logging are first-class concerns.
7. Resume and cover-letter generation are template-driven. The backend/resume_templates/ and backend/cover_letter_templates/ directories contain multiple auto-discovered themes, each with metadata and Jinja templates. The backend renders these into exportable documents, and the README indicates PDF generation is done through Playwright.
8. Tracking and follow-up are separate async subsystems. backend/email_monitor/ connects to Gmail via OAuth and parses replies to update application state, while backend/notifier/telegram.py sends alerts, daily digests, and scrape-health notifications. backend/job_monitor.py and backend/scheduler.py coordinate periodic jobs such as scraping, email checks, cleanup, and other maintenance tasks.
9. The frontend is a single-page app built with React 18, Vite, Tailwind, and Recharts. Its components map closely to backend domains: job feed, application board, company manager, resume builder, cover-letter builder, persona, search manager, settings, and stats. The frontend consumes the backend API and is responsible for interactive review, filtering, drag-and-drop status changes, and editing content.
10. The Chrome extension is a separate capture surface, not just a UI add-on. Under extension/, the content script and background service worker support passive LinkedIn capture and “send job from any page” behavior, giving the system a browser-side ingestion path that bypasses the normal scraper when needed.
11. The data model is centered on jobs, companies, searches, resumes, applications, and personas, with status transitions and scoring artifacts persisted in PostgreSQL. The tests show important behaviors like deduplication, caching, best-score selection, job-run targeting, company alias filtering, and transition history, which suggests the app treats these as durable workflow entities rather than ephemeral search results.
12. External service boundaries matter to the architecture: job boards/ATS pages, MyVisaJobs for H-1B data, Gmail for inbox monitoring, Telegram for notifications, and LLM providers for scoring/tailoring. The system is designed so each of these can fail independently without collapsing the core app, which is why scraping, AI, tracking, and messaging are separated into distinct backend modules.
Main entry points: models Data model · api Domain routes · orchestrator.py Ingest orchestrator · analyzer Analyzer
Components 19
Core backend
Python API, ingestion, analysis, persistence, and background automation
- API entry · Application startup and server wiring
backend/main.py - Domain routes · Jobs, companies, searches, resumes, applications, cover letters, persona, settings
backend/api - Data model · Persistent entities and database access
backend/models - Ingest orchestrator · Coordinates source capture, normalization, deduplication, and filtering
backend/scraper/orchestrator.py - Shared scrape · Browser control, URL safety, normalization, deduplication, and analysis helpers
backend/scraper/_shared - ATS adapters · Workday, Greenhouse, Lever, Ashby, Oracle HCM, Phenom, TalentBrew, Rippling, SmartRecruiters, Google, Meta, and generic pages
backend/scraper/ats - Source adapters · Company pages, JobSpy, Levels.fyi, Jobright, LinkedIn personal collections, and extension feeds
backend/scraper/sources - Analyzer · Salary extraction, H-1B lookup, CV scoring, cover-letter generation, and LLM-backed analysis
backend/analyzer - LLM boundary · Anthropic, OpenAI, Ollama, and OpenAI-compatible calls with logging and cost tracking
backend/analyzer/llm_client.py - Doc templates · Resume and cover-letter theme sets rendered into exportable documents
backend/resume_templates - Email monitor · Gmail OAuth, reply parsing, and application-state updates
backend/email_monitor - Notifier · Telegram alerts, digests, and scrape-health notifications
backend/notifier - Scheduler · Runs scraping, email checks, cleanup, and maintenance
backend/scheduler.py
Frontend
React/Vite user interface
- SPA shell · Vite/React/Tailwind front end and shared API client
frontend/src/App.jsx - Workflows UI · Job feed, application board, company manager, resume builder, cover-letter builder, persona, search manager, settings, and stats
frontend/src/components
Browser capture
Chrome extension for LinkedIn and page-based job capture
- Extension core · Background service worker and content script for passive LinkedIn capture
extension/background.js - Extension UI · Popup surface for sending jobs from any page
extension/popup.html
Runtime and deploy
Local/self-hosted packaging and edge exposure
- Compose stack · Docker Compose, backend/frontend images, and local self-hosted runtime
docker-compose.yml - Edge proxy · Caddy/nginx exposure for the hosted stack
Caddyfile
Connections 25
- API entry → Domain routes: mounts · Registers API surface on startup
- Domain routes → Data model: reads/writes · Persists jobs, companies, searches, resumes, applications, and persona state
- Domain routes → Ingest orchestrator: triggers · API actions can start or manage ingestion workflows
- Ingest orchestrator → Shared scrape: uses · Normalizes URLs, controls browser sessions, deduplicates, and filters results
- Ingest orchestrator → ATS adapters: dispatches · Routes career-page scraping to the appropriate ATS adapter
- Ingest orchestrator → Source adapters: ingests · Pulls jobs from aggregators, personal collections, and extension feeds
- Shared scrape → Data model: produces · Converts heterogeneous captures into normalized persisted records
- ATS adapters → Data model: produces · Stores normalized job listings
- Source adapters → Data model: produces · Stores normalized job listings from external sources
- Domain routes → Analyzer: invokes · Runs scoring, tailoring, and job enrichment on demand
- Analyzer → LLM boundary: calls · Uses provider-agnostic LLM operations for analysis and document generation
- Analyzer → Doc templates: renders · Builds resumes and cover letters from theme templates
- Analyzer → Data model: updates · Writes scores, extracted data, and generated artifacts
- Email monitor → Data model: updates · Applies reply parsing to application status
- Notifier → Data model: reads · Builds alerts and digests from persisted workflow state
- Scheduler → Ingest orchestrator: runs · Schedules recurring ingestion jobs
- Scheduler → Email monitor: runs · Schedules mailbox polling and reply processing
- Scheduler → Notifier: runs · Schedules digests and health notifications
- SPA shell → Domain routes: consumes · Interactive client for the API boundary
- Workflows UI → SPA shell: composes · Feature screens inside the SPA
- Extension core → Source adapters: feeds · Browser-side LinkedIn capture becomes an ingestion source
- Extension UI → Extension core: sends jobs · Popup forwards pages/jobs to the background worker
- Compose stack → API entry: runs · Containerizes the API service
- Compose stack → SPA shell: runs · Containerizes the UI service
- Compose stack → Edge proxy: exposes · Routes local/self-hosted traffic through the proxy layer