Skip to main content

Site-agnostic job-portal monitor with pluggable adapters and Telegram alerts

Project description

๐Ÿ›ก Job Sentinel

Site-agnostic job-portal monitor with pluggable adapters and instant Telegram alerts.

CI Python 3.11+ License: MIT Ruff uv Conventional Commits

Job Sentinel monitors job-listing portals on a configurable interval. The moment a new posting appears, you get a rich Telegram alert โ€” with title, employer, location, deadline, keyword tags, and a direct link.

It ships with adapters for UTD 12twenty and Handshake. Adding a new portal takes one file and ~50 lines of Python.

Out of the box it watches UTD 12twenty's on-campus Student Employment tab. The tab is chosen by the tab= parameter in PORTAL_JOBS_URL, so the same setup later points at internships or full-time listings by switching that URL.


โœจ Features

Feature Details
Pluggable adapters One Python file per portal โ€” no core changes needed
Telegram bot Rich alerts + commands (/jobs, /applied, /stats, /deadlines, โ€ฆ)
Rรฉsumรฉ engine Universal profile โ†’ ATS-friendly LaTeX/PDF, tailored per posting
Local-LLM tailoring Optional Ollama rephrasing โ€” no API key, nothing leaves your machine
Web UI Next.js + Tailwind app: profile editor, rรฉsumรฉ studio, jobs board
Local API FastAPI layer (job-sentinel serve) the UI consumes โ€” one source of truth
Email + Telegram alerts Two notifier channels; email is optional SMTP
Deadline awareness /deadlines flags postings closing within a configurable window
Status tracking NEW โ†’ SEEN โ†’ APPLIED / IGNORED / CLOSED, persisted in SQLite
Closed detection Marks postings that disappear from the portal
Production-grade mypy --strict, ~82% tests, CI (lint/types/tests/secret/supply-chain), Docker

๐Ÿ— Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        Job Sentinel                            โ”‚
โ”‚                                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚   Scheduler  โ”‚โ”€โ”€โ”€โ–ถโ”‚   Adapter   โ”‚โ”€โ”€โ”€โ–ถโ”‚  JobRepository   โ”‚  โ”‚
โ”‚  โ”‚  (APSchedulerโ”‚    โ”‚  (Playwrightโ”‚    โ”‚  (sqlite-utils)  โ”‚  โ”‚
โ”‚  โ”‚   background)โ”‚    โ”‚   + site    โ”‚    โ”‚                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚   plugin)   โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚         โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜             โ”‚            โ”‚
โ”‚         โ”‚                                        โ”‚            โ”‚
โ”‚         โ–ผ                                        โ–ผ            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚   Telegram   โ”‚โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚   Bot Handlers       โ”‚ โ”‚
โ”‚  โ”‚   Notifier   โ”‚   alerts + cmds    โ”‚  (python-telegram-   โ”‚ โ”‚
โ”‚  โ”‚   (httpx)    โ”‚                    โ”‚   bot v21, async)    โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

See docs/design/HLD.md for the full High-Level Design and docs/design/LLD.md for Low-Level Design.


๐Ÿš€ Quick Start

1. Prerequisites

  • Python 3.11+
  • uv (install: curl -LsSf https://astral.sh/uv/install.sh | sh)
  • A Telegram account

2. Clone & Install

git clone https://github.com/harshitwandhare/job-sentinel.git
cd job-sentinel

# Install all dependencies (creates .venv automatically)
uv sync

# Install Playwright's Chromium browser
uv run playwright install chromium

3. Create your Telegram bot

  1. Open Telegram and message @BotFather
  2. Send /newbot and follow the prompts โ†’ copy the token
  3. Message your new bot once, then visit: https://api.telegram.org/bot<TOKEN>/getUpdates
  4. Copy your chat ID from the JSON response (message.chat.id)

4. Configure

cp .env.example .env
# Edit .env and fill in:
#   TELEGRAM_BOT_TOKEN=...
#   TELEGRAM_CHAT_ID=...
#   PORTAL_USERNAME=your_utd_netid
#   PORTAL_PASSWORD=your_password

5. Test run (dry run โ€” no messages sent)

uv run job-sentinel scrape

6. Start the full bot

uv run job-sentinel run

That's it. Open Telegram and send /start to your bot.


โš™๏ธ Configuration

All configuration is via environment variables in .env. See .env.example for the full reference.

Variable Default Description
TELEGRAM_BOT_TOKEN โ€” Required. From @BotFather
TELEGRAM_CHAT_ID โ€” Required. Your Telegram user/chat ID
PORTAL_USERNAME โ€” Required. Portal login
PORTAL_PASSWORD โ€” Required. Portal password
PORTAL_JOBS_URL UTD 12twenty URL Full URL to the listings page
SITE_ADAPTER 12twenty Adapter to use
POLL_INTERVAL_SECONDS 900 Scrape interval (min: 60)
KEYWORD_FILTERS (empty = all) CSV: software,engineer,research
HEADLESS true Run browser headless
DRY_RUN false Scrape but don't send alerts
LOG_LEVEL INFO DEBUG/INFO/WARNING/ERROR

๐Ÿค– Bot Commands

Command Description
/jobs Trigger a fresh scrape + show recent postings
/recent Show last 10 jobs from the database
/applied <id> Mark posting as applied
/ignore <id> Dismiss a posting
/status <id> Full details of a specific posting
/stats Counts by status (new / seen / applied / ignored / closed)
/deadlines Postings closing within DEADLINE_ALERT_DAYS
/filters Show active keyword filters
/adapters List available site adapters
/ping Health check

๐Ÿ“„ Rรฉsumรฉ Generator

Job Sentinel keeps a universal profile โ€” your master CV data in one hand-editable YAML file โ€” and renders ATS-friendly PDFs from it. It's standalone: you don't need the Telegram bot configured to use it.

# 1. Scaffold a profile you can edit like an Overleaf source
uv run job-sentinel resume init        # writes data/profile.yaml

# 2. Edit data/profile.yaml โ€” add education, experience, projects, skillsโ€ฆ

# 3. Build an ATS-friendly PDF (also writes the .tex next to it)
uv run job-sentinel resume build -o data/resume.pdf
uv run job-sentinel resume show        # summarise your profile

# 4. Tailor to a specific posting โ€” reorders content by relevance and
#    reports ATS keyword coverage (matched vs missing terms)
uv run job-sentinel resume build --job-text "paste a job description here"
uv run job-sentinel resume build --job-id <posting_id>   # a posting already scraped

# 5. Generate a tailored cover letter (deterministic, or --ai to polish locally)
uv run job-sentinel resume cover --job-text "โ€ฆ" --role "Student Assistant" --company "UTD"

PDF rendering uses Tectonic (a self-contained LaTeX engine โ€” no full TeX install needed). Install it once:

winget install TectonicProject.Tectonic   # Windows
brew install tectonic                      # macOS
cargo install tectonic                     # Linux (or your package manager)

If Tectonic isn't installed, resume build still writes the .tex so you can compile it on Overleaf. The template is single-column with standard fonts and real selectable text, so it parses cleanly through ATS.

Optional: local-LLM rephrasing (no API key)

Add --ai to rephrase your bullets toward a posting using a local model via Ollama โ€” fully offline, no API key, your data never leaves your machine. It only rephrases content already in your profile (it can't invent facts), and falls back to keyword tailoring if the model isn't available.

job-sentinel resume doctor --pull      # checks Ollama + pulls the model
job-sentinel resume build --ai --job-text "paste a job description"

๐Ÿ–ฅ Web UI

Prefer a UI? Job Sentinel ships a local web app (Next.js + Tailwind) over a FastAPI layer โ€” same engine, nicer surface. It's fully local: the API binds to localhost and the optional LLM stays on your machine.

# 1. Start the local API (needs the 'web' extra: uv sync --extra web)
job-sentinel serve                 # http://127.0.0.1:8000  (Swagger at /docs)

# 2. Start the web app
cd web && npm install && npm run dev   # http://localhost:3000

Pages: an animated landing, a profile editor, a rรฉsumรฉ studio (paste a JD โ†’ live ATS coverage โ†’ download a tailored PDF, with a local-LLM toggle), and a jobs board with statuses and deadlines.


๐Ÿ”Œ Adding a New Portal

  1. Create src/job_sentinel/adapters/sites/my_portal.py
  2. Subclass SiteAdapter, implement login() and scrape_page()
  3. Set SITE_ADAPTER=my_portal in your .env

Full guide: docs/design/adapter-authoring.md


๐Ÿ›  Development

# Install dev dependencies
uv sync --all-extras

# Install pre-commit hooks (runs ruff, mypy, secret scan on every commit)
uv run pre-commit install

# Run tests
uv run pytest

# Lint & format
uv run ruff check --fix .
uv run ruff format .

# Type check
uv run mypy src/

# All at once (same as CI)
uv run pre-commit run --all-files

๐Ÿณ Deployment (always-on) & data persistence

Run it continuously with Docker โ€” the image is based on the official Playwright image (Chromium + system libs included):

docker compose up -d --build     # start detached
docker compose logs -f           # follow

Your data never vanishes on restart. ./data and ./logs are bind-mounted from the host, so the SQLite database, captured login session, and your profile live on disk โ€” surviving container restarts, rebuilds, and reboots.

12twenty's login is Cloudflare-gated, so capture a session on the host first with job-sentinel login (it writes data/session.json, which the container mounts and reuses). Re-run login if the session expires.

Backups. Everything important is in data/. Back it up while the bot is idle โ€” e.g. a WAL-safe SQLite copy:

sqlite3 data/jobs.db ".backup data/jobs.backup.db"
cp data/profile.yaml data/profile.backup.yaml

๐Ÿ“ Project Structure

job-sentinel/
โ”œโ”€โ”€ src/job_sentinel/
โ”‚   โ”œโ”€โ”€ adapters/
โ”‚   โ”‚   โ”œโ”€โ”€ base.py            # Abstract SiteAdapter interface
โ”‚   โ”‚   โ”œโ”€โ”€ registry.py        # Plugin registry (dynamic loading)
โ”‚   โ”‚   โ””โ”€โ”€ sites/
โ”‚   โ”‚       โ”œโ”€โ”€ twelve_twenty.py   # UTD 12twenty adapter
โ”‚   โ”‚       โ””โ”€โ”€ handshake.py       # Handshake adapter
โ”‚   โ”œโ”€โ”€ bot/
โ”‚   โ”‚   โ””โ”€โ”€ handlers.py        # Telegram command handlers
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ”œโ”€โ”€ settings.py        # pydantic-settings config
โ”‚   โ”‚   โ””โ”€โ”€ logging.py         # loguru setup
โ”‚   โ”œโ”€โ”€ core/
โ”‚   โ”‚   โ”œโ”€โ”€ browser.py         # Playwright lifecycle manager
โ”‚   โ”‚   โ”œโ”€โ”€ models.py          # JobPosting, ScrapeResult (Pydantic v2)
โ”‚   โ”‚   โ””โ”€โ”€ scheduler.py       # APScheduler poll loop
โ”‚   โ”œโ”€โ”€ db/
โ”‚   โ”‚   โ””โ”€โ”€ repository.py      # sqlite-utils DB layer
โ”‚   โ”œโ”€โ”€ notifiers/
โ”‚   โ”‚   โ””โ”€โ”€ telegram.py        # MarkdownV2 formatting + delivery
โ”‚   โ””โ”€โ”€ __main__.py            # Typer CLI entry-point
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ unit/                  # Fast, no I/O
โ”‚   โ”œโ”€โ”€ integration/           # Real DB, mocked network
โ”‚   โ””โ”€โ”€ e2e/                   # Full stack (optional, requires .env)
โ”œโ”€โ”€ docs/
โ”‚   โ”œโ”€โ”€ adr/                   # Architecture Decision Records
โ”‚   โ””โ”€โ”€ design/                # HLD, LLD, adapter authoring guide
โ”œโ”€โ”€ scripts/                   # Dev helper scripts
โ”œโ”€โ”€ .github/workflows/         # CI/CD (GitHub Actions)
โ”œโ”€โ”€ pyproject.toml             # Single source of truth (uv + hatchling)
โ”œโ”€โ”€ .env.example               # Config template
โ””โ”€โ”€ .pre-commit-config.yaml    # Ruff, mypy, gitleaks, conventional commits

๐Ÿ“‹ Roadmap

  • Rรฉsumรฉ engine (universal profile โ†’ ATS LaTeX/PDF) with per-posting tailoring
  • Local-LLM rephrasing via Ollama (no API key)
  • Web UI (Next.js) + local FastAPI layer
  • Email notifier (optional SMTP) alongside Telegram
  • Deadline-aware tracking (/deadlines)
  • Docker / docker-compose with persistent data
  • Cover-letter generation (local LLM)
  • Semantic relevance ranking (local embeddings)
  • More portal adapters (Greenhouse, Workday, public boards via JobSpy)
  • Discord webhook notifier
  • Packaged installers + PyPI publish

๐Ÿค Contributing

Contributions are welcome! Please read CONTRIBUTING.md first.

All commits must follow Conventional Commits. Run uv run pre-commit install to enforce this automatically.


๐Ÿ“„ License

MIT ยฉ Harshit Wandhare โ€” see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

job_sentinel-0.4.0.tar.gz (158.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

job_sentinel-0.4.0-py3-none-any.whl (79.4 kB view details)

Uploaded Python 3

File details

Details for the file job_sentinel-0.4.0.tar.gz.

File metadata

  • Download URL: job_sentinel-0.4.0.tar.gz
  • Upload date:
  • Size: 158.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for job_sentinel-0.4.0.tar.gz
Algorithm Hash digest
SHA256 8648d13d457b45fc807f672fb29fcb3b7bed45e2e0d628ab3f8b95c0c2971a8a
MD5 9769cd4f5d1ceade6fe9b1af82a64215
BLAKE2b-256 cf855a5d3bf024d42491814fda6404f10ac076ef3462f35da5f8ebda4d6700bb

See more details on using hashes here.

File details

Details for the file job_sentinel-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: job_sentinel-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 79.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for job_sentinel-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5117c2bf2c4bc32ff41caaf1210cf96465793cdb078c61ebd4d2166938a74af
MD5 a78b65a878d91c952ebb77deef58ddcb
BLAKE2b-256 12f94cd0dccd6577d79c49353e5e59a8d92c1768be7ae0fb007c78c16d3fd430

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page