A Telegram bot that finds developer jobs from your plain-text description of what you want.
Job boards expect developers to navigate filters, checkboxes, and keyword fields. StackHire removes that friction — you describe your ideal role in plain English inside Telegram, and it queries the Adzuna job API live, scores every result against your tech stack, role type, seniority, and location, and returns ranked listings with direct apply links. Your preferences are persisted (with hashed IDs for privacy) so repeat queries get smarter over time.
Live demo → stackhire.tanisheesh.in — landing page and docs. Telegram bot → t.me/StackHireBot — no login, no signup, start typing.
- Natural language query parsing — extracts tech stack, role type, seniority, location, and remote preference from free-text without an LLM; fully deterministic and auditable.
- DB-first, live-fallback search — serves cached Adzuna results for speed; falls back to a live API call when fewer than 15 matches exist for your specific query.
- Weighted relevance ranking — scores each listing across five dimensions (tech tags, role type, seniority, location, remote) and returns the top matches sorted by score.
- Per-user preference profiles — saves your last query as a profile (SHA-256 hashed Telegram ID) so future empty queries fall back to your saved stack and preferences.
- Paginated results with "Show more" — delivers 5 results per page via Telegram inline keyboard; background scraper refreshes the DB every 6 hours and marks stale listings expired.
| Layer | Tech |
|---|---|
| Bot runtime | Bun 1.x · TypeScript 5 · Telegraf 4 |
| Web (landing) | Next.js 16 · React 19 · Tailwind CSS 4 |
| Database | PostgreSQL 16 · postgres (npm) |
| Job data | Adzuna REST API |
| Scraping | Playwright (Chromium) — LinkedIn, Indeed, Remotive scrapers (built, not wired in v1) |
| Infra | Docker · Docker Compose · Render |
Why Bun over Node.js? Bun's built-in TypeScript runner removes the transpile step, ships a native test runner, and is faster for I/O-heavy workloads — no extra tooling required.
Why deterministic scoring over an LLM for matching? Every score is traceable to its inputs: +2 per matching tech tag, +3 for role type, +3 for location, etc. No prompt tuning, no rate limits, no hallucinated relevance. The tradeoff is that it can't understand synonyms ("JS" vs "JavaScript") — mitigated by the parser's canonical keyword map.
Why DB-first with live fallback, rather than always querying the API? The Adzuna free tier has rate limits. Serving the background-scraped cache for common queries is instant and free; the live call only fires when the cache is thin for an unusual query/location combination.
Why SHA-256 hash Telegram user IDs before storage? Telegram user IDs are stable identifiers. Hashing before writes means the DB never contains a value that can be reverse-mapped to a real identity — preference profiles are linked only to the hash.
| Document | Description |
|---|---|
| PRD | Product requirements — goals, user stories, non-goals |
| Architecture | System design, data flow, component breakdown |
| Decisions | Every major technical decision and why |
| Setup | Local dev setup, env vars, deployment |
Tanish Poddar — tanisheesh.in · LinkedIn · GitHub