A backend that scrapes multiple job boards, classifies each posting by tech-stack relevance and remote eligibility, and broadcasts matches across Telegram, Email and WhatsApp — with an interactive Telegram bot, a scheduled scrape/notify pipeline and an admin dashboard. Built to surface remote PHP/Laravel roles open to candidates in Azerbaijan.
Relevant remote jobs are scattered across many boards, buried under on-site and off-stack noise, and gone by the time you check manually. HirePath automates the whole funnel: it harvests postings from several sources on a schedule, runs each through a relevance & remote-eligibility classifier (is it really PHP/Laravel? is it truly remote? can someone in Azerbaijan actually take it?), de-duplicates and persists them, then pushes only the qualifying roles to subscribers on their channel of choice. An admin dashboard tracks what was found, sent, viewed and applied to.
- Resilient web scraping — an abstract
BaseScraper(Guzzle + browser-like headers, timeouts, graceful failure) with per-site implementations parsing real HTML via Symfony DomCrawler / CSS selectors. Dead/anti-bot sources are isolated so one broken board never breaks the run. - Domain classification engine — a single
JobRelevanceFilter(one source of truth, reused by every scraper, the aggregator service and abot:reclassifycommand) encoding non-trivial rules: PHP/Laravel signals, excluded stacks, remote vs hybrid vs on-site, worldwide / EMEA / Europe / country-restricted detection, Azerbaijan eligibility, aneeds_reviewfallback for ambiguous geography, and a seniority-weighted priority score. - Multi-channel notification fan-out — Telegram (per-subscriber + channel broadcast), Email (queued Mailable) and WhatsApp, each with rate-limit-aware pacing and per-channel error isolation.
- Interactive bot — a webhook-driven Telegram bot with a command router (
/start,/stop,/help,/latest,/email,/whatsapp) that manages subscriptions and per-user delivery preferences. - Scheduled pipelines — Laravel scheduler runs
bot:fetchevery two hours andbot:sendfive minutes later; idempotent "unsent / viewed / applied" state prevents duplicate delivery. - Pragmatic data modelling — rich query scopes (
featured,available,needsReview,seniority,unsent) and additive migrations that evolve the schema as classification logic grows. - Server-rendered admin UI — Blade + Tailwind (Vite) dashboard with view/applied tracking, plus correct CSRF exemption for the inbound Telegram webhook.
| Layer | Technology |
|---|---|
| Language / Framework | PHP 8.3+, Laravel 13 |
| Scraping | guzzlehttp/guzzle, symfony/dom-crawler, symfony/css-selector |
| Messaging | Telegram Bot API (irazasyed/telegram-bot-sdk + raw API), WhatsApp (kstmostofa/laravel-whatsapp), SMTP email |
| Queue & scheduler | Laravel database queue + scheduler |
| Persistence | SQLite (default) / MySQL |
| Frontend | Blade + Tailwind CSS via Vite |
Laravel Scheduler (every 2h)
│
▼
bot:fetch ─────────────► JobScraperService
│ iterates registered scrapers
┌──────────────────────┼───────────────────────────┐
▼ ▼ ▼
JobsearchAzScraper HelloJobScraper LinkedInScraper / DjinniScraper …
│ (extends BaseScraper: Guzzle + DomCrawler)
▼
JobRelevanceFilter ── stack relevance + remote eligibility + priority score
│
▼
Vacancy (DB) ── de-duplicated, classified, scoped (unsent/featured/…)
│
bot:send (2h + 5m) ── only unsent + qualifying
│
├──► TelegramService → subscribers + channel
├──► Mail (VacancyNotification)
└──► WhatsApp
Inbound: POST /webhook/telegram → TelegramWebhookController → command router
Admin: /dashboard · /admin/vacancies · /admin/subscribers (Blade + Tailwind)
The classifier is the architectural keystone: scrapers, the aggregator and the reclassify command all delegate to it, so relevance rules live in exactly one place.
- Multi-source scraping — pluggable scrapers for jobsearch.az, hellojob.az, LinkedIn and Djinni (boss.az / isbazar isolated when unavailable); add a board by extending
BaseScraper. - Relevance & remote classification — keeps only PHP/Laravel + fully-remote + Azerbaijan-eligible roles; flags ambiguous geography for manual review; scores seniority/priority.
- Telegram bot — subscribe/unsubscribe, browse latest roles, and opt into Email/WhatsApp delivery, all via chat commands; HTML-formatted job cards.
- Channel broadcast — posts to a configured Telegram channel in addition to direct subscribers.
- Email & WhatsApp — queued email Mailable and optional WhatsApp Web delivery, each independently toggleable.
- Scheduled automation — hands-off fetch → classify → notify loop with duplicate-safe state.
- Admin dashboard — vacancy list with source/seniority/remote metadata, open/applied/viewed tracking, and a subscriber view.
| Method | Route | Description |
|---|---|---|
GET |
/ |
Landing page |
GET |
/dashboard |
Stats dashboard |
GET |
/admin/vacancies |
Admin vacancy list (featured/filtered) |
GET |
/admin/vacancies/{vacancy}/open |
Open + mark viewed |
PATCH |
/admin/vacancies/{vacancy}/applied |
Toggle applied status |
GET |
/admin/subscribers |
Subscriber list |
POST |
/webhook/telegram |
Inbound Telegram bot webhook (CSRF-exempt) |
php artisan bot:fetch # scrape all sources, classify, persist new vacancies
php artisan bot:send # broadcast unsent vacancies (Telegram + Email + WhatsApp)
php artisan bot:reclassify # re-run the relevance/remote classifier over stored vacancies
php artisan bot:set-webhook # register the Telegram webhook URLRequirements: PHP 8.3+, Composer 2, Node.js (Vite assets), and a Telegram bot token (from @BotFather).
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
npm install && npm run build # or: npm run dev
# Run the scheduler + queue worker (or use `composer dev` for an all-in-one dev loop)
php artisan schedule:work
php artisan queue:work
# Register the Telegram webhook (public HTTPS URL required)
php artisan bot:set-webhookIn production the scheduler runs from cron (* * * * * php artisan schedule:run), driving bot:fetch / bot:send automatically.
| Variable | Description |
|---|---|
DB_CONNECTION |
sqlite (default) or mysql |
QUEUE_CONNECTION |
database — queued mail / async work |
TELEGRAM_BOT_TOKEN |
Bot token from @BotFather |
TELEGRAM_CHANNEL_ID |
Channel to broadcast vacancies to |
MAIL_MAILER / MAIL_HOST / MAIL_PORT |
SMTP transport for email notifications |
MAIL_USERNAME / MAIL_PASSWORD |
SMTP credentials (use an app password) |
MAIL_FROM_ADDRESS / MAIL_FROM_NAME |
Email sender identity |
WHATSAPP_WEB_ENABLED |
Toggle WhatsApp delivery on/off |
WHATSAPP_WEB_HOST / _PORT / _TOKEN |
WhatsApp Web gateway connection |
APP_URL |
Public URL — used to build the Telegram webhook endpoint |
.envand*.logare git-ignored. The committed.env.examplecontains placeholders only — no real tokens or credentials.
Web scraping (Guzzle + Symfony DomCrawler / CSS selectors) with an extensible BaseScraper abstraction · domain-driven relevance & remote-eligibility classification (single source of truth) · Telegram Bot API (webhook ingestion + command routing + channel broadcast) · multi-channel notification fan-out (Telegram / Email / WhatsApp) with rate-limit pacing and per-channel error isolation · Laravel scheduler & queues for an automated fetch→classify→notify pipeline · queued Mailables · idempotent delivery state · Eloquent query scopes & incremental migrations · Blade + Tailwind admin dashboard · secure webhook handling (targeted CSRF exemption).
MIT