JD Matcher is an intelligent job matching tool that leverages Large Language Model (LLM) capabilities to find the most suitable jobs based on user resumes and job descriptions. The project provides services through a Telegram bot, automatically crawling job listings and notifying users when matching positions are found.
- π€ Intelligent Matching: Uses LLM-based embeddings (OpenRouter) for vector representations and DeepSeek for semantic matching
- π± Telegram Bot Integration: grammY-powered bot with pagination, file upload, and inline keyboards
- π·οΈ Automated Job Crawling: Supports RemoteOK and WeWorkRemotely job sources with scheduled crawling
- π Smart Notifications: Automatically notifies users when matching jobs are found via Telegram and email
- π§ Email Verification: Users can set and verify their email for email-based job notifications
- β‘ Cloud-Native: Built on Cloudflare Workers with D1 database, Vectorize search, Queue-based async jobs, and Container-based agent runtime
- π Fully Serverless: No infrastructure to manage, auto-scaling with Containers for long-running workloads
jd-matcher/
βββ src/
β βββ index.ts # Entry: Hono routes + scheduled() + queue()
β βββ lib/
β β βββ types.ts # All type definitions + Env bindings
β β βββ db/ # D1 CRUD (job_detail, user_info, user_matched_job, email_verification)
β β βββ llm/ # OpenRouter embeddings + DeepSeek chat + prompt
β β βββ crawler/ # RemoteOK + WeWorkRemotely scrapers
β β βββ email/ # Email HTML/text templates
β β βββ vectorize/ # Vectorize upsert/query helpers
β βββ container/
β β βββ server.ts # HTTP server wrapping runMatchAgent for Container runtime
β β βββ server.test.ts # Integration tests
β βββ bot/
β β βββ bot.ts # grammY setup + env middleware + command registration
β β βββ session.ts # KV-backed chat session (10min TTL)
β β βββ constants.ts # All Telegram reply texts
β β βββ handlers/ # start, help, all_jobs, jobs, upload_resume, expectation
β βββ jobs/
β βββ crawl.ts # Fetch jobs from RemoteOK + WeWorkRemotely
β βββ embed.ts # Generate embeddings β store in Vectorize
β βββ match.ts # Vector search β AI agent (Vercel AI SDK) β store matches
β βββ match.test.ts
β βββ notify.ts # Unnotified matches β Telegram + email
βββ migrations/
β βββ 001_initial.sql # D1 schema
β βββ 002_email_verification.sql # Email verification table
βββ wrangler.toml # Single config β D1, KV, Vectorize, Queue, Cron
βββ package.json
βββ tsconfig.jsonCron βββΆ scheduled() βββΆ JOBS_QUEUE.send({type}) βββΆ queue() β dispatch
Telegram βββΆ Hono POST /telegram/webhook βββΆ grammY bot βββΆ command handlers
Match agent βββΆ MatchContainer (Cloudflare Containers) βββΆ runMatchAgent (Vercel AI SDK)
- Node.js 20+ (for local development)
- Wrangler CLI (
npx wrangler) - Cloudflare account
- Telegram Bot Token (from @BotFather)
- OpenRouter API Key
- DeepSeek API Key
-
Clone the repository
git clone https://github.com/chenjunqian/jd-matcher.git cd jd-matcher -
Install dependencies
npm install
-
Set up Cloudflare resources
# Create D1 database npx wrangler d1 create jd-matcher-db # Copy the database_id from output into wrangler.toml # Run migration npx wrangler d1 execute jd-matcher-db --file migrations/001_initial.sql # Run email verification migration npx wrangler d1 execute jd-matcher-db --file migrations/002_email_verification.sql # Create KV namespace npx wrangler kv:namespace create "SESSION_KV" # Copy id into wrangler.toml [[kv_namespaces]] # Create Vectorize indexes npx wrangler vectorize create job-desc-embeddings --dimensions=1024 --metric=cosine npx wrangler vectorize create resume-embeddings --dimensions=1024 --metric=cosine # Create job queue npx wrangler queue create jd-jobs-pool
-
Set secrets
npx wrangler secret put TELEGRAM_BOT_TOKEN npx wrangler secret put LLM_OPENROUTER_APIKEY npx wrangler secret put LLM_DEEPSEEK_APIKEY
-
Deploy
npx wrangler deploy
Note: The
MatchContainerrequires a Dockerfile at the project root. The container image is built and deployed automatically withwrangler deploy. -
Set Telegram webhook
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://jd-matcher.<subdomain>.workers.dev/telegram/webhook"
# Start local dev server with bindings
npx wrangler dev
# Register webhook for local testing (requires tunnel)
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://your-tunnel.ngrok.io/telegram/webhook"| Command | Description |
|---|---|
/start |
Start the bot |
/help |
Usage help |
/all_jobs |
Browse all available jobs (paginated) |
/jobs |
Browse your matched jobs (paginated) |
/upload_resume |
Upload your resume (text file) |
/expectation |
Set job expectations (location, salary, language, etc.) |
/email |
Set email address and verify for email notifications |
- Resume Upload: Users upload their resumes as text files through the Telegram bot
- Vector Embedding: Resumes are converted to vector representations using OpenRouter embeddings
- Job Crawling: Cron triggers crawl jobs from RemoteOK and WeWorkRemotely every 2 hours
- Embedding Generation: New jobs are embedded via Queue consumer and stored in Vectorize
- Matching: Vector search finds similar jobs, then the AI agent (Vercel AI SDK) performs semantic ranking. The agent runs inside Cloudflare Containers (
MatchContainer) to support long-running LLM calls. - Notifications: Users are notified via Telegram and/or email when matching jobs are found
- Email Verification: Users can set their email via
/emailcommand, receive a verification link, and opt into email notifications
npm run dev # Start wrangler dev server
npm run dev:cron # Dev server with test-scheduled flag
npm run deploy # Deploy to Cloudflare Workers
npm run test # Run vitest tests
npm run typecheck # TypeScript type check- Create a new crawler file in
src/lib/crawler/ - Export the fetch function
- Add it to the crawl pipeline in
src/jobs/crawl.ts
- Add the provider client in
src/lib/llm/ - Add environment variables to
src/lib/types.tsEnvinterface - Add the API call to the appropriate job handler
- Add handler in
src/bot/handlers/ - Register in
src/bot/bot.tswithbot.command() - Add reply text to
src/bot/constants.ts
npx vitest run # Run all tests
npx vitest run --reporter=verbose # Verbose output
# Type checking
npm run typecheck| Variable | Required | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | Telegram bot token from @BotFather |
LLM_OPENROUTER_APIKEY |
Yes | OpenRouter API key for embeddings |
LLM_DEEPSEEK_APIKEY |
Yes | DeepSeek API key for job matching |
| Binding | Type | Description |
|---|---|---|
EMAIL |
send_email |
Cloudflare Email Service for sending verification and notification emails |
| Variable | Required | Default | Description |
|---|---|---|---|
APP_URL |
Yes | β | Public app URL for email verification links (e.g. https://jdmatcher.guoshaotech.com) |
| Variable | Default | Description |
|---|---|---|
LLM_OPENROUTER_BASEURL |
https://openrouter.ai/api/v1 |
OpenRouter API endpoint |
LLM_OPENROUTER_MODEL |
deepseek/deepseek-v3.2 |
Chat model for OpenRouter |
LLM_OPENROUTER_EMBEDDINGMODEL |
qwen/qwen3-embedding-8b |
Embedding model |
LLM_DEEPSEEK_BASEURL |
https://api.deepseek.com/v1 |
DeepSeek API endpoint |
LLM_DEEPSEEK_MODEL |
deepseek-v4-flash |
DeepSeek chat model |
LLM_DEEPSEEK_REASONINGEFFORT |
high |
DeepSeek reasoning effort |
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.
- Cloudflare Workers - Serverless compute platform
- Hono - Lightweight web framework
- grammY - Telegram bot framework
- OpenRouter - Unified LLM API gateway
- DeepSeek - LLM provider
- RemoteOK - Remote job board
- WeWorkRemotely - Remote job board
If you have any questions or issues, please:
- Check the Issues page
- Create a new issue if needed
β If this project helps you, please give it a star!