<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mit Parekh – Data & Automation]]></title><description><![CDATA[Mit Parekh – Data & Automation]]></description><link>https://mitp1997.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 13:24:31 GMT</lastBuildDate><atom:link href="https://mitp1997.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Build a free daily job alert from Google Jobs with n8n]]></title><description><![CDATA[Email yourself new Google Jobs listings for any search once a day, with no duplicates, using n8n, Apify and Gmail. Fits free tiers. Template included.]]></description><link>https://mitp1997.hashnode.dev/google-jobs-daily-alert-n8n</link><guid isPermaLink="true">https://mitp1997.hashnode.dev/google-jobs-daily-alert-n8n</guid><category><![CDATA[n8n]]></category><category><![CDATA[automation]]></category><category><![CDATA[jobs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[apify]]></category><dc:creator><![CDATA[Mit Parekh]]></dc:creator><pubDate>Sat, 24 Oct 2026 14:02:47 GMT</pubDate><content:encoded><![CDATA[<p>Google Jobs is the job box you see when you search for something like "data analyst jobs new york". It collects postings from LinkedIn, Indeed, ZipRecruiter, Monster, company career pages and many smaller boards, so it is one of the widest job searches available.</p>
<p>What it doesn't have is a good alert. Google's own "notify me" option is tied to your Google account and sends what Google decides is relevant.</p>
<p>This tutorial builds a simple alternative:</p>
<ul>
<li><strong>Every morning you get one email</strong> with the jobs that appeared since yesterday for your search.</li>
<li><strong>No duplicates.</strong></li>
<li><strong>No email</strong> on days when nothing is new.</li>
</ul>
<p>It takes about 15 minutes.</p>
<p><em>Disclosure: I built the Google Jobs scraper used here and earn money when people use it on Apify.</em></p>
<h2>What you need</h2>
<ul>
<li><strong>n8n.</strong><ul>
<li>The self-hosted Community Edition is free.</li>
<li>n8n Cloud is a paid service with a free trial.</li>
</ul>
</li>
<li><strong>An Apify account.</strong> The free plan includes $5 of usage credit per month and doesn't need a card.</li>
<li><strong>An email account.</strong><ul>
<li>On n8n Cloud, the Gmail node connects with one click.</li>
<li>On self-hosted n8n, Gmail needs your own Google OAuth app. The plain <strong>Send Email</strong> (SMTP) node works with any provider and is simpler.</li>
</ul>
</li>
</ul>
<h2>How it works</h2>
<pre><code>Schedule (daily 08:00)
  -&gt; Apify: Google Jobs Scraper (only new jobs)
  -&gt; Code: build one HTML email (or nothing)
  -&gt; Gmail: send
</code></pre>
<p>The important part is that the scraper <strong>remembers which jobs it already returned</strong>:</p>
<ol>
<li>You set <code>onlyNewJobs: true</code> and a <code>stateKey</code> (any name you like).</li>
<li>The Actor saves the IDs of returned jobs in a key-value store in your Apify account.</li>
<li>On the next run, jobs it has already seen are skipped. You are <strong>not charged</strong> for them.</li>
</ol>
<p>That gives you deduplication for free, without a database or a "seen jobs" sheet.</p>
<h2>Step 1: Add the Apify node</h2>
<ul>
<li><strong>n8n Cloud:</strong> open the nodes panel and search for <strong>Apify</strong>. It's a verified community node. If it doesn't show up, the instance owner may need to allow verified community nodes.</li>
<li><strong>Self-hosted:</strong> go to <strong>Settings -&gt; Community Nodes</strong>, then install <code>@apify/n8n-nodes-apify</code>.</li>
</ul>
<p>Next, create an <strong>Apify API</strong> credential. Copy your token from Apify Console -&gt; Settings -&gt; API &amp; Integrations.</p>
<h2>Step 2: Configure the search</h2>
<p>Add a <strong>Schedule Trigger</strong> set to every day at 08:00.</p>
<p>Then add the Apify node and set it up:</p>
<ul>
<li><strong>Operation:</strong> Run actor and get dataset</li>
<li><strong>Actor:</strong> <code>mitp1997/google-jobs-scraper</code> (or paste the ID <code>13xUasuMvo5caeSvY</code>)</li>
<li><strong>Input JSON:</strong></li>
</ul>
<pre><code class="language-json">{
  "queries": ["data analyst"],
  "location": "New York",
  "country": "US",
  "maxJobsPerQuery": 10,
  "onlyNewJobs": true,
  "stateKey": "n8n-template-data-analyst-nyc",
  "includeDescription": false
}
</code></pre>
<p>A few notes on the fields:</p>
<ul>
<li><strong><code>queries</code></strong> can hold several job titles. Each one is searched separately.</li>
<li><strong><code>country</code></strong> sets the Google market and the language. The scraper has been tested in the US, UK, Canada, India, Germany, France, Spain, Mexico, Brazil, Italy and the Netherlands.<ul>
<li>Google Jobs isn't available everywhere. Australia shows normal web results instead, and you get a free <code>unavailable</code> row rather than junk.</li>
</ul>
</li>
<li><strong><code>maxJobsPerQuery</code></strong> is an upper limit. Google returns about 10 jobs per search page, and the Actor searches variants to find more.</li>
<li><strong><code>stateKey</code></strong> should be unique per alert. Use a different key for "data analyst NYC" and "product manager remote".</li>
</ul>
<p>Under the node's options, set <strong>Memory</strong> to 256 MB. The Actor doesn't use a browser, so that's enough. Also set <strong>Maximum cost per run</strong>. The template uses $0.10.</p>
<h2>Step 3: Build the email</h2>
<p>Add a <strong>Code</strong> node in "Run once for all items" mode:</p>
<pre><code class="language-js">const jobs = $input.all().map(i =&gt; i.json).filter(j =&gt; j.status === 'ok');
if (jobs.length === 0) return []; // nothing new -&gt; no email

const esc = s =&gt; String(s ?? '').replace(/[&amp;&lt;&gt;"]/g,
  c =&gt; ({ '&amp;': '&amp;amp;', '&lt;': '&amp;lt;', '&gt;': '&amp;gt;', '"': '&amp;quot;' }[c]));

const items = jobs.map(j =&gt; {
  const link = j.applyLink || j.applyOptions?.[0]?.url || j.shareLink;
  const meta = [j.company, j.location, j.via &amp;&amp; `via ${j.via}`, j.postedAgo]
    .filter(Boolean).map(esc).join(' | ');
  const salary = j.salary ? `&lt;div&gt;Salary: ${esc(j.salary)}&lt;/div&gt;` : '';
  return `&lt;li&gt;&lt;a href="${esc(link)}"&gt;&lt;b&gt;${esc(j.title)}&lt;/b&gt;&lt;/a&gt;&lt;div&gt;${meta}&lt;/div&gt;${salary}&lt;/li&gt;`;
});

return [{ json: {
  subject: `${jobs.length} new "${jobs[0].query}" jobs on Google Jobs`,
  html: `&lt;ol&gt;${items.join('')}&lt;/ol&gt;`,
}}];
</code></pre>
<p>Returning an empty array when nothing is new means the Gmail node simply doesn't run.</p>
<p>Finally, add a <strong>Gmail -&gt; Send</strong> node with:</p>
<ul>
<li><strong>Subject:</strong> <code>{{ $json.subject }}</code></li>
<li><strong>Email type:</strong> HTML</li>
<li><strong>Message:</strong> <code>{{ $json.html }}</code></li>
</ul>
<h2>What you actually get</h2>
<p>Here is the first run of exactly this input, from 24 October 2026. It returned 10 jobs, including:</p>
<ul>
<li><strong>Data Analyst 3</strong>, New York State Thruway Authority, via Monster: <em>111,708-137,590 a year</em></li>
<li><strong>Product Data Analyst</strong>, Novig, via Built In NYC: <em>130K-180K a year</em></li>
<li><strong>Data Analyst II, Maternal Health Policy and Equity</strong>, Mount Sinai Health Systems, via the employer's careers site</li>
<li><strong>Junior Functional Data Analyst</strong>, Yale University, via ZipRecruiter</li>
<li><strong>Lead Data Analyst (Remote), Growth Analytics</strong>, Ruby Labs, flagged as work-from-home</li>
</ul>
<p>Some details about that run:</p>
<ul>
<li><strong>Salary.</strong> Half of the 10 jobs had a salary listed. The scraper also splits it into <code>salaryMin</code>, <code>salaryMax</code>, <code>salaryCurrency</code> and <code>salaryPeriod</code>, so you can filter on it.</li>
<li><strong>Apply links.</strong> Each job has a direct apply link, plus all the other boards it's posted on in <code>applyOptions</code>.</li>
<li><strong>Later runs.</strong> Because of <code>onlyNewJobs</code>, tomorrow's run won't repeat these 10. It only returns jobs it hasn't seen for this <code>stateKey</code>.</li>
</ul>
<h2>What it costs</h2>
<ul>
<li><strong>Price:</strong> $3 per 1,000 jobs, which is $0.003 per job. There is no start fee, and rows that explain an empty search are free.</li>
<li><strong>Daily cap:</strong> with <code>maxJobsPerQuery: 10</code>, the most you can spend is $0.03 per day, or about $0.90 a month. Quiet days cost less, because already-seen jobs aren't charged.</li>
<li><strong>Free tier:</strong> that fits in Apify's $5 monthly free credit with room for a few more alerts.</li>
</ul>
<h2>Ideas to extend it</h2>
<ul>
<li><strong>Several alerts:</strong> duplicate the Apify node with different queries and <code>stateKey</code>s, or put all queries in one run.</li>
<li><strong>Filter before emailing:</strong> in the Code node, keep only <code>workFromHome === true</code>, or <code>salaryMin &gt;= 100000</code>.</li>
<li><strong>Send it somewhere else:</strong> swap Gmail for Slack, Telegram or Discord. The Code node output works the same.</li>
<li><strong>Keep a log:</strong> add a Google Sheets "append" step to build a history for salary research.</li>
<li><strong>Read full descriptions:</strong> set <code>includeDescription: true</code> if you want to filter on keywords inside the posting.</li>
</ul>
<h2>Troubleshooting</h2>
<ul>
<li><strong><code>no_results</code> row.</strong> Google answered "no matches" for that search. Broaden the location or use the local language for non-English markets.</li>
<li><strong>Fewer jobs than <code>maxJobsPerQuery</code>.</strong> That usually just means there weren't more <em>new</em> jobs. Small markets run out quickly.</li>
<li><strong>Reset the alert.</strong> Delete the key in the <code>google-jobs-scraper-state</code> key-value store in Apify Console, or change <code>stateKey</code>.</li>
</ul>
<h2>Resources</h2>
<ul>
<li>n8n workflow template: submitted to the n8n template library (link coming once it's approved)</li>
<li>Google Jobs Scraper on Apify: <a href="https://apify.com/mitp1997/google-jobs-scraper">https://apify.com/mitp1997/google-jobs-scraper</a></li>
<li>Apify n8n node: <a href="https://docs.apify.com/integrations/n8n">https://docs.apify.com/integrations/n8n</a></li>
</ul>
<hr />
<p><em>This article was drafted with help from an AI assistant and tested against live runs of the Actor. The job listings shown come from a real run on 24 October 2026.</em></p>
]]></content:encoded></item></channel></rss>