Skip to content

Repository files navigation

Automated Job Applications

A truthful, human-in-the-loop job-application agent (Python package: apply-agent). Pick a job posting, watch it tailor your resume to the posting without fabricating anything, fill the form, and wait for your Telegram approval before it ever clicks Submit. Runs on Gemini's free tier — $0/mo.

CI License: MIT Python 3.11+ Runtime cost: $0/mo Validator: sacred

What this is

A hand-selected applier, not a bulk applier. Target: 15–20 carefully chosen applications/day, hard cap 25. For each application, the agent:

  1. Detects the ATS (Greenhouse, Lever, Ashby, Workable, SmartRecruiters; falls back to a vision-driven Tier-2 loop for Workday / iCIMS / custom).
  2. Picks the best resume variant from your resumes/ folder and tailors it to the JD with Gemini Flash.
  3. Validates the tailored output: every employer, year, tool, and metric must trace to your source resume. Hallucinations get rejected, regenerated, then escalated to you.
  4. Renders to PDF with a UUID stamped into the metadata so you can A/B response rates per variant.
  5. Persists the application, posts a Telegram approval card with bullet-level diffs, and waits for ✅ Submit.
  6. On approval, fills the form (still doesn't click Submit unless you pass --no-dry-run).
  7. Monitors the inbox for responses. Interview / recruiter / offer emails trigger immediate high-priority Telegram alerts with excerpt + matched application context.

It gets smarter every time you intervene. Every answer you supply through Telegram goes into a semantic qa_log and is reused (verbatim above 0.85 similarity, rephrased between 0.70 and 0.85, escalated below).

What this is not

  • Not a bulk applier. The pre-flight rejects past 25/day, refuses outside 10am–6pm in your local submit window, and enforces a 90-day cooldown on (company, role).
  • Not a Submit-by-default tool. dry_run=True is the safety default. You explicitly opt in with --no-dry-run once you've watched a dry run.
  • Not a resume rewriter. The validator rejects any tailored output that introduces facts not in the source .docx/.md — no embellishment, no fabricated metrics.
  • Not a LinkedIn Easy Apply bot. LinkedIn is Tier-3 (manual takeover) only. The agent prepares everything and hands you the wheel.

Features

Area What you get
ATS adapters Greenhouse, Lever, Ashby, Workable, SmartRecruiters (Tier-1 deterministic). Workday / iCIMS / custom (Tier-2 vision loop). LinkedIn (Tier-3 manual).
Truthfulness Sacred validator with entity extraction (years, acronyms, proper nouns), 2-retry regenerate, then human escalation.
HITL Telegram approval before every Submit. Inline buttons for ✅ Submit / ✗ Reject / 📷 Screenshot / ✏ Edit. Pause/resume on uncertainty.
Learning qa_log semantic store. Local bge-small-en-v1.5 + sqlite-vec for cosine ranking. Reuse > 0.85, rephrase 0.70-0.85, pause < 0.70.
Email monitoring IMAP IDLE listener. Signup-verify correlator + 4–8-digit OTP extractor. Gemini classifier into 6 response categories. High-priority alerts on interview/recruiter/offer.
Encryption Per-row age encryption of portal credentials. Master key never written to logs. CSV mirror at secrets/portal_passwords.csv for human read.
Pre-flight Submission window, daily cap, 90-day cooldown, salary floor, H1B sponsor pre-filter, Gemini quota headroom.
Observability Per-variant response-rate dashboard. Daily 7pm-local digest. Training-run analyzer that proposes new qa_log seeds every 10 apps.
Backup Nightly tar.gz of secrets/, data/, and the DB. Keeps last 30.
Cost $0/mo runtime. Gemini 2.5 Flash free tier (15 RPM / 1500 RPD / 1M TPM).

Quickstart (5 minutes)

# Inside WSL2 Ubuntu (or any Linux with Python 3.11+):
git clone https://github.com/drajb/Job-Application-Automation.git apply-agent
cd apply-agent
make dev-install
cp .env.example .env             # fill in keys as you get them
make keygen                      # creates secrets/master.age.key — BACK IT UP
cp profile.example.yaml secrets/profile.yaml
$EDITOR secrets/profile.yaml     # fill in your details
make encrypt-profile             # encrypts to .age, removes plaintext
make migrate
make test                        # 68+ tests should pass
make run                         # boots Telegram bot + IMAP + scheduler

From Telegram:

/apply https://boards.greenhouse.io/<some-company>/jobs/<id>

You'll get an approval card. The agent stops at dry-run by default; pass --no-dry-run only after you've verified the tailored output is good.

See docs/QUICKSTART.md for the unhurried walkthrough.

How safe is it?

Risk Mitigation
Hallucinated resume content Sacred validator rejects unseen entities. 2-retry hard fail, then Telegram escalation.
Bot detection on hardened ATSes Tier-3 manual takeover. The agent does all prep, you finish in a real browser window.
Telegram bot hijack chat_id allowlist enforced on every command + callback.
Wrong company submission Pre-submit screenshot + Telegram approval card with bullet diffs. Mandatory.
Credential leak age-encrypted vault. Master key in secrets/master.age.key (gitignored). Decrypted only in memory after process start.
Captcha Always escalate to Tier-3 takeover. No third-party solvers.
Phishing in verification email Sender domain match + intentional expectation lifecycle.
Wrong submission window Hard refuse outside 10am–6pm in the configured timezone.
Quota exhaustion Rate tracker pauses new applications at 95% of Gemini's daily RPD.

Architecture (one-pager)

┌─────────────────────────────────────────────────────────────────┐
│                       YOU (Telegram, mobile)                     │
└──────────┬──────────────────────────────────────────▲───────────┘
           │ /apply <url>, /handoff, /done, etc.      │ approvals,
           ▼                                          │ inbox alerts
┌─────────────────────────────────────────────────────────────┐
│  apply-agent (single Python process, src/main.py)            │
│   ├─ Telegram bot (python-telegram-bot, long polling)        │
│   ├─ IMAP IDLE listener (imap_tools, any IMAP server)        │
│   ├─ FastAPI internal endpoints (127.0.0.1:8080)             │
│   ├─ Orchestrator (apply_to)                                 │
│   │    ├─ Resume selector → tailor → validator → renderer    │
│   │    ├─ Pre-flight (window, cap, cooldown, salary, quota)  │
│   │    └─ Telegram approval gate                             │
│   ├─ Execution router (Tier 1 / Tier 2 / Tier 3)             │
│   ├─ Account vault (age + CSV mirror)                        │
│   ├─ Email handler (signup correlator + classifier)          │
│   └─ SQLite + sqlite-vec (apply_agent.db)                    │
│                                                                │
│  Local Chromium (Playwright, dedicated profile)               │
│  Local bge-small-en-v1.5 (embeddings)                         │
│  Local LibreOffice (docx → pdf)                               │
└─────────────────────────────────────────────────────────────┘

Full diagram + module-by-module breakdown in docs/ARCHITECTURE.md.

Configuration

All knobs are env vars. See docs/CONFIGURATION.md for the full list. Minimum to run:

Var Required for Where to get it
GEMINI_API_KEY Resume tailoring + classifier https://aistudio.google.com/apikey
TELEGRAM_BOT_TOKEN HITL approval @BotFather on Telegram
TELEGRAM_CHAT_ID HITL approval getUpdates after messaging your bot
APPLY_EMAIL_USER Email monitoring + signup verify A dedicated job-application email
APPLY_EMAIL_PASSWORD Email monitoring + signup verify App password (enable 2FA first)
APPLY_EMAIL_IMAP_HOST Email monitoring Defaults to imap.gmx.com
RESUME_SOURCE_DIR Resume selection Defaults to ./resumes/

Missing keys cause clear startup warnings, not crashes. The affected subsystem disables itself and the rest of the agent runs.

Make targets

Target What it does
make install venv + runtime deps
make dev-install + pytest/ruff/mypy
make run python -m src.main (polling)
make test pytest
make lint ruff check
make migrate apply alembic migrations
make keygen generate secrets/master.age.key (run ONCE; back it up)
make encrypt-profile encrypt secrets/profile.yaml.age, remove plaintext
make passwords print path to secrets/portal_passwords.csv
make clean wipe caches; secrets and DB untouched

Where to read next

  1. docs/QUICKSTART.md — get from zero to your first dry-run application in 30 minutes.
  2. docs/SPEC.md — the locked architectural decisions. Read before opening a substantive PR.
  3. docs/ARCHITECTURE.md — diagrams, module boundaries, data flow.
  4. docs/CONFIGURATION.md — every env var and CLI flag.
  5. docs/ATS_SUPPORT.md — supported ATSes + how to add a new one.
  6. docs/PRIVACY.md — what data is stored, where, and how encryption works.
  7. docs/FAQ.md — common questions.

Contributing

PRs welcome — especially new ATS adapters, prompt improvements, and additional pre-flight checks. See CONTRIBUTING.md for the workflow + how to add a new ATS adapter.

Found a vulnerability? See SECURITY.md for responsible disclosure.

License

MIT. Use it, fork it, ship it. Attribution appreciated but not required.

Acknowledgements

Built with Gemini 2.5 Flash, Playwright, python-telegram-bot, imap-tools, bge-small-en-v1.5, sqlite-vec, age, and LibreOffice headless.

About

Job Application Automator

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages