The problem
In one month, LinkedIn sent me 63 job-alert emails with 282 job links. Despite LinkedIn’s own filters, most were noise: ads in languages I don’t speak, industries I’d rather not work in (gambling, CFD trading, quick-money crypto), and “remote” jobs that weren’t remote for me (but for example, only within certain countries or time zones), or companies that looked more like a pitch deck than a business. Of 76 “remote” jobs the pipeline analysed, only 19 were open to anyone, anywhere. And of the ads it fetched, 42% were already closed.
Reading them all by hand is slow, and keyword filters can’t capture what I actually care about: a role that fits my skills and situation, at a company that has a positive impact on the world.
What I built
A pipeline that reads the alerts for me. It collects every job from my inbox, removes duplicates, and uses an LLM at each step:
- to understand the job: role, company, location, how remote it really is, and the languages it requires;
- to judge the company’s impact;
- to read the full ad;
- to match the job against a personal profile I wrote in plain language: my skills, my dealbreakers, my minimum rate.
The output is a shortlist sorted best first. Each job gets a FIT, MAYBE or NO, a score from 0 to 10, the pros and cons, and the reason behind the verdict. I run it whenever I want a fresh list.
I built it step by step with Claude Code, and there’s still some work to do.
How it works
Every judgement is made by an LLM (the OpenAI API with structured outputs), with its reason saved:
- Gmail API (read-only OAuth) finds the LinkedIn alert emails and extracts every job link.
- SQLite stores each job once. Every step processes only what’s new, so re-runs are nearly free.
- An LLM turns each alert line into clean fields: job title, company, location, work mode (as probabilities) and required languages.
- The same LLM rates each company’s impact on the world: positive, neutral or negative, with a reason. If it’s negative, it names the harm (gambling, CFD trading…). Each company is judged only once, even if it posts several jobs.
- A first filter drops only the evident cases: harmful companies, languages I don’t speak, roles I’ve ruled out. Borderline jobs go on.
- The full ad of each kept job is fetched once, at a polite pace, and saved.
- The LLM then reads the full ad: what the company does, where the job can really be done from, contract and hours, seniority, languages, salary and a one-line summary.
- Finally, it matches the job against my profile (see right below) and returns FIT, MAYBE or NO, with a score and a reason. Editing the profile re-scores everything automatically.
My profile
The last step only works because the AI knows what I’m looking for. That’s why the AI and I wrote it down together, in plain language:
- Three tracks: SEO work that pays the bills, roles that build real machine learning experience, and applied AI with LLMs.
- Dealbreakers: what rules a job out, however good the rest of it looks.
- Exceptions: when a dealbreaker can be overlooked.
- A minimum rate: annual salaries are converted to an hourly rate, so every offer can be compared.
- Nice-to-haves: what makes a good job better.
It’s an ordinary text file, so changing my mind costs nothing: I edit a line, and every job is scored again.
What I learned
- Read what’s written, not what’s implied. The first prompt guessed languages from the city name: a job in Berlin “required” German. A stricter prompt fixed it: only languages actually named in the ad count.
- The model can see a problem and still miss the rule. It wrote “requires German C2” and still answered MAYBE. It noted “known for forex and CFD trading” and still called the company neutral. The fix: a separate answer for harms and dealbreakers, which the code enforces whatever the verdict.
- Clear categories are stable; fuzzy ones aren’t. Gambling, CFDs and crypto got the same label in five runs out of five. “Ethically controversial” flipped from run to run, so it moved to the profile step, where there’s more context.
- A wrong drop costs more than a miss. A job dropped early is lost for good, while one let through by mistake costs one more step and gets caught later. So the early filters stay deliberately loose.
- Reason first, answer after. Asking the model to explain before giving its verdict made the answers more consistent.
- Bigger isn’t always better. A test showed the larger model wasn’t more reliable here, so the whole pipeline runs on a cheaper one.
Results
On one real run, the funnel looked like this:
- 312 jobs collected from my inbox
- 265 left after the first filter
- 155 still open when their ads were fetched
- 61 unique jobs scored against my profile (the same ad posted in several cities counts once)
- 4 FIT, 24 MAYBE, 33 NO
So instead of reading 312 job ads, I read four, and skim twenty-four if I have time. A typical FIT looks like this:
FIT, 8/10: remote contractor role building AI agents with retrieval and tool use. My projects match well, though it’s full-time.
What it could do for you
The same pattern works for any inbox that drowns your team: leads, job applications, support tickets, supplier offers. It reads everything, extracts what matters into structured data, filters by your rules, and hands you a ranked shortlist with reasons, so people only read what deserves attention. In my case, 312 job ads became 4 worth reading.
