AI Job Search — Telegram Notification Fork
This project is a personal fork of MadsLorentzen/ai-job-search, an open-source AI-assisted job search toolkit. The upstream repo is a well-crafted system for CV management, salary lookup, cover letter generation, and portal scraping. All credit for the core framework goes to Mads Lorentzen and contributors.
The fork at okfriansyah-moh/ai-job-search adds one focused capability on top: an automated daily Telegram notification pipeline that pushes ranked job matches directly to your phone without requiring you to open any dashboard or run any command manually.
Problem
The upstream project already scrapes job portals and ranks results, but you still had to check the output manually.
System Requirements
This fork closes that delivery gap by introducing:
- A Telegram bot client built on the standard library (no third-party SDK)
- A ranked job digest formatted as HTML cards
- Deduplication to avoid re-notifying about jobs you've already seen
- Retry-safe outbox with failure classification
- Scheduler support for cron, launchd, GitHub Actions, and AI coding agents
Architecture
Portal Scrapers → merge_jobs() → rank_job() → send_digest() → Telegram Bot API
↓
NotificationDeduper
(SQLite-backed fingerprints)
The pipeline runs as a single daily invocation of automation/run_daily.py. Each stage is idempotent — re-running on the same date is a no-op unless --force is passed.
Implementation
Telegram Delivery (automation/telegram.py)
The Telegram module is self-contained with zero external dependencies beyond Python's standard library. It communicates with the Telegram Bot API directly via urllib.request.
Job Cards
Each new job match is rendered as a structured HTML message:
lines = [
f"<b>{title}</b> · {company_line}",
f"<b>Fit:</b> {score} · {verdict}",
f"<b>Job location:</b> {job_location}",
f"<b>Work category:</b> {work_mode}",
f"<b>Level:</b> {seniority} · <b>Employment:</b> {employment_type}",
f"<b>Compensation:</b> {salary}",
f"<b>Posted:</b> {posted_date} · <b>Deadline:</b> {deadline}",
f"<b>Source:</b> {portal}",
]
Cards include match score, rank verdict, location, work mode (Remote / Hybrid / On-site), seniority, salary, posting date, deadline, and a direct application link — everything you need to triage a job without opening a browser.
Message Batching
The Telegram API has a 4096-character message limit. The module splits card lists into batches capped at CARD_BUDGET = 3500 characters to leave room for the digest header and footer:
MAX_MESSAGE = 4096
CARD_BUDGET = 3500
Deduplication
A NotificationDeduper tracks SHA-256 fingerprints of every sent notification. Before each send, the digest checks which jobs have already been delivered and skips them — so running the pipeline twice in a day won't flood the chat.
Retry Safety
Delivery errors are classified as retryable or non-retryable:
class TelegramDeliveryError(RuntimeError):
def __init__(self, message: str, *, retryable: bool):
super().__init__(message)
self.retryable = retryable
Failed batches are held in an outbox and retried on the next run, rather than silently dropped.
Daily Runner (automation/run_daily.py)
The run_daily.py script ties all stages together and is the single entrypoint for every scheduler:
# Runs with any of these schedulers:
--scheduler codex | cursor | claude | copilot | cron | launchd | manual
The run sequence:
- Acquire a file lock to prevent concurrent runs
- Check if today's run already succeeded (idempotency gate)
- Scrape enabled portals for jobs posted in the last 14 days
- Merge with the existing
seen_jobs.jsonstate, skipping duplicates - Rank each new job against your profile
- Call
send_digest()to push Telegram cards - Write success state with a full summary JSON
summary = {
"fetched": len(fetched),
"new": len(new_jobs),
"ranked": len(ranked),
"portals": { ... },
"telegram": telegram,
}
The --dry-run flag previews the full pipeline without writing state or sending any messages — useful for testing portal configuration.
Scheduler Support
The automation/schedulers/ directory includes ready-made scheduler configs:
| File | Purpose |
|---|---|
github-actions.yml.example | Run daily at 08:00 via GitHub Actions |
com.ai-job-search.daily.plist.example | macOS launchd daily job |
com.ai-job-search.telegram.plist.example | macOS launchd for Telegram listener |
The GitHub Actions scheduler is the most portable option — it requires no local machine and runs for free on public repositories.
Setup
git clone https://github.com/okfriansyah-moh/ai-job-search
cd ai-job-search
# Set required environment variables
export TELEGRAM_BOT_TOKEN="your-bot-token"
export TELEGRAM_CHAT_ID="your-chat-id"
# Test the pipeline without sending
python -m automation.run_daily --dry-run
# Run for real
python -m automation.run_daily
Create a Telegram bot via @BotFather, send /start to your new bot, and retrieve your chat_id using automation/telegram_setup.py.
What the Upstream Repo Provides
Everything not described above comes from the excellent upstream project by Mads Lorentzen:
- Portal scrapers — LinkedIn, Jobindex, and community-contributed portals
- AI ranking — LLM-powered job-fit scoring against your CV
- Salary lookup — Converts salary data across regions and currencies
- CV pipeline — LaTeX CV with moderncv, cover letter generation
- Tracker — CSV-based application status tracker with deadline tracking
- HTML report — Local report rendering from tracker data
If you're building a job search workflow from scratch, start with the upstream repository. This fork is only interesting if you specifically want the Telegram push layer on top.
Failure Modes
- Telegram API delivery failures (retryable and non-retryable)
- Oversized digest payloads if batching is not enforced
- Duplicate notifications when deduplication state is missing or corrupted
- Scheduler overlaps without a lock and idempotency gate
Lessons Learned
- A deterministic daily runner needs delivery, not just ranking, to be useful in practice
- Idempotency + deduplication is essential for notification-based automation
- Explicit retry classification keeps failures visible without creating message spam
Related
- Automation patterns used in this project — idempotent daily runners and state machines
- MD-AME — another autonomous pipeline with scheduled execution
- Delivery Foundry — pipeline orchestration patterns