Daily JSearch job tracker with Google Sheets log and Slack recap

Every weekday at 7am ET, pull fresh job postings from JSearch for each saved search, log the new ones to a Google Sheet, and post a Slack recap.

Deterministic Code
JSearchGoogle SheetsSlack BotHR & PeopleOperationsDaily DigestsResearch & MonitoringData Sync
PromptCreate

Build a code workflow that runs every weekday at 7am America/New_York and keeps a Google Sheet up to date with fresh job postings from JSearch, then posts a Slack recap. Pure deterministic pipeline: read, fetch, diff, append, notify.

Trigger: cron, Monday through Friday at 07:00 ET.

Inputs the workflow should expose as configuration: the Google Sheets spreadsheet id, the name of the Queries tab (default "Queries"), the name of the Tracked tab (default "Tracked"), and the Slack channel id for the recap.

Step 1 — Read saved searches from Google Sheets using Get Values on the Queries tab. Each row holds a query string in column A (for example "senior backend engineer remote") plus optional filter columns: employment_types (full-time, part-time, contract, intern, comma-delimited) and date_posted (today, 3days, week, month). Skip the header row and skip any row with an empty query.

Step 2 — Read the existing tracked job ids from Google Sheets using Get Values on the Tracked tab, column A only (the job_id column). Build an in-memory set of ids already seen so we can dedupe.

Step 3 — For each row from step 1, call JSearch Search Jobs with that row's query and filters. Use page=1 and num_pages=1 unless overridden. Note that JSearch expects employment_types and date_posted as query string params, and the location should be embedded inline in the query string (the API has no separate location parameter). Collect each returned posting's job_id, job_title, employer_name, job_city/job_state/job_country, job_posted_at_datetime_utc, job_min_salary/job_max_salary/job_salary_currency/job_salary_period, and job_apply_link.

Step 4 — Dedupe. Drop any posting whose job_id is already in the set from step 2. Also dedupe across queries within this run so a posting that matches two saved searches is only logged once (keep it under the first query that matched, for the recap).

Step 5 — Append the new rows to the Tracked tab using Append Values with valueInputOption USER_ENTERED. Columns, in order: job_id, title, company, location (formatted "City, State, Country" with empty parts omitted), posted_at (ISO date), salary_range (formatted like "$120k–$150k / year" when min and max are present, otherwise the single value or empty), apply_url.

Step 6 — Post one Slack message via Slack Bot Send a Message to the configured channel. Format with Slack mrkdwn. Headline line: "Fresh roles for {weekday date}: {total new} new". Then one section per query that produced results, in the form "*{query}* — {count} new", followed by up to 5 bullet lines linking the top postings as "<{apply_url}|{title}> at {company}". Omit queries that returned zero new postings. If the entire run produced zero new postings across every query, still post a short "No new roles today" message so the channel knows the watcher is alive.

Error handling: if JSearch returns a 429 or 5xx for a particular query, log the failure and continue with the next query rather than aborting the whole run; surface failed queries at the bottom of the Slack recap as "Could not fetch: {query}".

Integrations to use: jsearch (Search Jobs), google-sheets (Get Values, Append Values), slackbot (Send a Message).

Additional information

What does this prompt do?
  • Runs every weekday morning and pulls live job listings for each saved search you keep in a Google Sheet.
  • Skips any posting you have already tracked so the sheet only ever grows with brand new roles.
  • Logs each new posting with the title, company, location, date posted, salary range, and apply link in one neat tab.
  • Posts one Slack recap per run showing how many new roles landed per search, with the top five titles linked for a quick scan.
What do I need to use this?
  • A JSearch account on RapidAPI for the live job feed (covers LinkedIn, Indeed, Glassdoor, ZipRecruiter, and more).
  • A Google Sheet with a Queries tab for your saved searches and a Tracked tab for the running log.
  • A Slack workspace and the channel where you want the morning recap to land.
How can I customize it?
  • Change the schedule from weekdays at 7am ET to whatever cadence fits your hiring rhythm.
  • Add or remove rows in the Queries tab to widen, narrow, or rotate the searches without touching the workflow.
  • Tune each query with filters like remote only, employment type, or posted within the last 24 hours.
  • Pick a different Slack channel for the recap or split recaps by hiring manager or function.

Frequently asked questions

Where do the job postings actually come from?
JSearch aggregates listings from Google for Jobs, which pulls from LinkedIn, Indeed, Glassdoor, ZipRecruiter, and a long tail of company career sites. You get one clean feed instead of checking each board.
How does it avoid showing me the same job twice?
Before appending anything to the Tracked tab, the workflow reads the job ids already in that tab and only writes rows that are not already there. Reruns and overlapping queries stay clean.
Can I run more than one saved search?
Yes. Each row in the Queries tab is its own search. Add a row for 'senior backend engineer remote', another for 'product manager fintech New York', and the workflow processes them all in one run.
What shows up in the Slack message?
A single recap message with a count of new postings per query and the top five titles linked through to the apply page. One ping per morning, not five hundred.
Do I need a paid JSearch plan?
The free RapidAPI tier covers 200 requests per month, which is enough for a handful of queries on weekdays. Higher volume or many saved searches usually needs the Pro tier.

Stop refreshing job boards every morning.

Connect JSearch, Google Sheets, and Slack once, and Geni delivers a deduped list of fresh roles every weekday at 7am.