Ryan Smith
production2026-09

Every New Job Post, Graded Before 6am

Scrapes job boards every morning, scores each posting against one candidate's real record, emails the shortlist

3,117 in 11 daysPostings tracked
8 to 24 per runWorth applying to
7 hoursShortlist to interview

The Problem

A serious job search means reading five hundred to eight hundred fresh postings a day across LinkedIn and Indeed, and almost none of them fit. The same jobs reappear day after day under slightly different titles, so you re-read work you already did. Judging fit honestly is the hard part: it is easy to talk yourself into a role that will screen you out, and easy to skip one you would actually win.

What It Does

Every morning before 6am it runs 25 saved searches across LinkedIn and Indeed, pulling only postings from the last 24 hours. It folds both sources into one common record and collapses the duplicates inside the run, then throws out anything non-US, on-site outside the local area, or carrying a title it has already learned to reject. What survives is checked against everything already tracked, so repeats only get their last-seen date bumped instead of being re-read. Genuinely new postings go to a language model carrying a written scoring rubric, which grades fit 1 to 10 and names every requirement met, partially met, and missing. The results land in Airtable and arrive as one email: apply, maybe, and a collapsed list of everything skipped, with the day's counts at the top.

Anyone running a long, specific job search who wants a short honest list waiting for them in the morning instead of a feed to scroll. It works hardest for a candidate with an unusual record that keyword search handles badly.

Outcome

In eleven days it graded and tracked 3,117 postings and put 8 to 24 worth applying to on the list each morning. On the first day the shortlist was worked, every application was submitted by noon, an interview request came in at 13:07 that same afternoon, and the interview happened the next day. That interview came from a posting the first version of the rubric had scored a 6, below the apply line, and a later rescore moved up.

Screenshots

The 2026-09-09 morning digest: 805 scraped, 298 past the prefilter, 268 new, 24 worth applying to, with scored cards showing the fit summary, the hard must-have tally and the flags.
The 2026-09-09 morning digest: 805 scraped, 298 past the prefilter, 268 new, 24 worth applying to, with scored cards showing the fit summary, the hard must-have tally and the flags.
The 2026-09-09 morning digest: 805 scraped, 298 past the prefilter, 268 new, 24 worth applying to, with scored cards showing the fit summary, the hard must-have tally and the flags.

The 2026-09-09 morning digest: 805 scraped, 298 past the prefilter, 268 new, 24 worth applying to, with scored cards showing the fit summary, the hard must-have tally and the flags.

The Run Log, one row per run from 2026-09-03 to 2026-09-14. The top row is the initial seven-day backfill; every row below it is a 24-hour run. The four straight days of zero in the Scored 7+ column are the failure that triggered the rubric rewrite.
The Run Log, one row per run from 2026-09-03 to 2026-09-14. The top row is the initial seven-day backfill; every row below it is a 24-hour run. The four straight days of zero in the Scored 7+ column are the failure that triggered the rubric rewrite.
The Run Log, one row per run from 2026-09-03 to 2026-09-14. The top row is the initial seven-day backfill; every row below it is a 24-hour run. The four straight days of zero in the Scored 7+ column are the failure that triggered the rubric rewrite.

The Run Log, one row per run from 2026-09-03 to 2026-09-14. The top row is the initial seven-day backfill; every row below it is a 24-hour run. The four straight days of zero in the Scored 7+ column are the failure that triggered the rubric rewrite.

The Jobs table: every posting kept with its score, lane, recommendation and fit summary, not just the ones worth applying to.
The Jobs table: every posting kept with its score, lane, recommendation and fit summary, not just the ones worth applying to.
The Jobs table: every posting kept with its score, lane, recommendation and fit summary, not just the ones worth applying to.

The Jobs table: every posting kept with its score, lane, recommendation and fit summary, not just the ones worth applying to.

How It's Built

Trigger:Schedule trigger, cron 45 5 * * *, daily at 05:45 Central. The workflow's timezone is set to America/Chicago so every timestamp it writes is Central.
Components:19

A code node builds the run's search plan: one item per source and search term, each carrying the scraper actor ID and its input payload, so changing what gets searched is a config edit rather than a rewiring. A batch loop feeds those three at a time to an HTTP node calling Apify's run-sync endpoint, with continue-on-error so one dead actor call does not take down the run. A normalize node folds the two very different actor response shapes into one record, detects US location and work arrangement by regex, converts hourly pay to annual, and collapses duplicates inside the run on a normalized company plus title key. A prefilter node then applies cheap rule-based drops before anything costs money, tagging every item pass or drop with a reason rather than silently discarding it. Survivors are checked against existing dedupe keys pulled from Airtable, and a switch splits them three ways: new, re-seen, or nothing at all. New ones are wrapped one at a time into a chat completions call carrying the scoring rubric as the system prompt and a strict JSON schema as the response format. A parse node re-applies the rubric's caps and floors in code, so pay, seniority, degree, core-craft and pivot rules hold even when the model forgets them, then emits Airtable field names directly. Re-seen postings only get Last Seen and Times Seen bumped. A three-input merge waits for every branch before the digest node builds the email HTML and the run log row, which go to Gmail and then to a Run Log table.

By the numbers

postings scraped per 24-hour run
472 to 805
postings scraped on the initial backfill run, 2026-09-03, which used a seven-day window instead of 24 hours
1,403
postings surviving the prefilter per 24-hour run
146 to 326
new postings scored per 24-hour run
121 to 280
postings scoring 7 or higher per run, since the v1.5 rubric
8 to 24
postings graded and held in the Jobs table as of 2026-09-14
3,117
repeats caught by dedupe rather than re-scored
518 postings seen more than once, 631 re-seen events
duplicates collapsed inside a single run before the prefilter, 2026-09-09
385 of 805 scraped, leaving 420 unique
rows rescored after the rubric audit
102
rescored rows that fell below the apply threshold
59 of 102, mean drop 1.2 points
applications submitted from the tracker in roughly the first week
46
searches per run
25 (11 terms across 2 sources, plus 3 local LinkedIn searches)
ceiling on postings pulled per run
955 (40 per search, 25 on the three local searches)
OpenAI spend across all of Ryan's workflows combined, not this one alone
about $3 per day

Key Decisions

Run at 05:45 Central

So the list is ready when he wakes up and is ready to apply. The work happens before the day starts, not during it.

Two filtering layers, regex before model

A title regex costs nothing and a model call does not. Anytime you can name a category of opportunity that will never be a fit, eliminate it ruthlessly and early. The prefilter kills healthcare, trucking, software engineering, early-career and non-US postings before a single token is spent.

Drops are tagged, not deleted

Every item carries pass or drop plus a reason all the way to the digest, so the email reports what was thrown out and why. Silent filtering is how you stop trusting your own pipeline.

Every run writes a row to a log table, whether or not it found anything

The counts at each stage are the only way to see the pipeline failing while it still looks healthy. Four straight mornings of zero recommendations is invisible in an inbox and obvious in a table.

The rubric's caps and floors are re-enforced in code after the model answers

The model is good at reading a posting and bad at remembering fifteen numeric rules. The model supplies the judgment, the code supplies the arithmetic.

Must-haves are tagged HARD or SOFT and only HARD ones move the score

Communication, analytical thinking and attention to detail are met by nearly everyone. Counting those the same as the actual craft is what let unqualified roles score high.

A too-junior posting is capped at 5, not boosted

Exceeding every requirement is a mismatch, not a fit. A twenty-year operator applying to a coordinator role gets screened as overqualified or hired underpaid.

A 9 or 10 requires the posting to name a tool he actually builds in

The top of the scale should mean something specific. If the posting does not name n8n, Make, Zapier, Airtable, Apps Script or custom GPTs, the score is capped at 8 no matter how well the prose reads.

Dedupe on normalized company plus title, not URL or source ID

The same job appears on both boards with different IDs and reposts with new URLs. Stripping punctuation, parentheticals and Inc/LLC is what makes a repeat look like a repeat.

Three searches per batch, 40 results per search, two scoring calls per four seconds

None of these were picked up front. They came down after rate limits showed up in testing. It is about keeping the workflow flowing smooth without flooding the system with too many at one time.

The pipeline always emits at least one item

A day with zero new postings still needs to write a run log row and send a digest. Empty output would skip both, and silence reads the same as a broken workflow.

The model call is the only per-posting cost, so the prefilter is the cost lever

Everything else in the run is effectively free. Quality is very high right now and the next version is about holding that quality while cutting the bill: more inexpensive filters first, then the expensive model on the nitty-gritty details. A posting that is clearly not a fit a third of the way in should stop there. There is no reason to finish grading every requirement to a qualified yes or no once the answer no longer matters.

What broke & how it was fixed

It was wrong in both directions, and the run log caught one of them before a human did. For four consecutive mornings, 2026-09-04 through 2026-09-07, it scraped between 472 and 790 postings and recommended zero. The scoring was trying to gate on too many things at once, so a posting had to clear every requirement at full strength to survive, and nothing did. At the same time, a manual deep screen of postings it HAD scored 8 or higher came back with 2s through 5s. Same root cause seen from two sides: soft-skill requirements were counted the same as the actual craft, so eight soft matches could carry a role over the line while a genuinely good role died on one strict gate. Three more causes turned up in the same audit on 2026-09-08: no seniority check, so junior and graduate-program roles scored high precisely because the candidate exceeded them; 'X or similar' counted as met even when the posting named several enterprise platforms he had never touched; and years in general marketing were being credited as years in a named specialty. Version 1.5 split must-haves into HARD and SOFT so only the craft lines move the score, added a core-craft check ahead of the tally, added a seniority field that caps at 5, raised the pay floor to $85K, and added an explicit floor rule that forces a 7 when every gate passes. A second audit the same day added 1.5.1, because postings listing a national location while the body said three days a week in the office were still getting through. A separate rescore workflow then re-ran 102 already-scored rows under the new rubric. Fifty-nine of them, 58 percent, dropped below the apply threshold, with an average fall of 1.2 points, and others rose. Every rescored row carries a note saying what it was, what rubric scored it, and what it is now.

Where else this applies

This is the shape of every high-volume screening problem: pull from sources with incompatible schemas, normalize to one record, drop the obvious no's with rules, spend model tokens only on survivors, and enforce the non-negotiable rules in code rather than trusting the model to remember them. Swap the rubric and it screens inbound leads, grant opportunities, RFPs, resumes against a role, or properties against buy-box criteria. The part worth stealing is the audit loop: a scoring system nobody spot-checks drifts, and it drifts toward being too generous, because a generous score feels like good news. Versioning the rubric and stamping the version on every row is what makes it possible to find out how wrong you were and fix the back catalog.