Automate outreach with n8n¶
For anyone comfortable with a spreadsheet. No coding needed. You leave with two working workflows: one drafts referral emails from a list of companies, one scores fresh jobs against your resume every morning.
I got tired of "networking" that meant copy-pasting the same DM to random people. What worked was short, credible emails to the right people about a specific role, with two proof points and a clear ask, so I automated the boring parts (The Referral Engine). I also learned that a one-size-fits-all resume never wins; the callbacks started once every application was tailored (Ultimate Job Search Workflow with n8n).
Watch out: Automation sends faster than you can read. This page sets every send step to Gmail drafts first. Do not schedule anything that emails real people until you have read the output of three manual runs.
The two workflows at a glance¶
| The Referral Engine | The Job Search Workflow | |
|---|---|---|
| Ascend post | The Referral Engine: n8n + Hunter + Gemini + Gmail (Oct 27, 2025) | Ultimate Job Search Workflow with n8n (Oct 12, 2025) |
| What it does | For each company in your sheet, finds up to 10 HR and IT contacts with Hunter, drafts a referral email per person with Gemini, logs them, then emails them with your resume | Pulls LinkedIn jobs posted in the last 24 hours that match your filter, scores each against your resume (0 to 100), writes a cover letter draft and a resume-edit list, then emails you |
| You give it | Company domain, job link, exact job title; your resume PDF | One filter row (keyword, location, level, remote); your resume PDF |
| You get | Results tab: Name, Email, Company, Subject, Email Body |
Result tab: Title, Company, Location, Link, Score, Cover Letter, Skills, Improvements |
| Trigger | Manual (click Execute workflow) | Daily at 5 AM |
| Run time (Jugal's note in the file) | About 5 minutes per company | About 1 hour |
| Accounts | Google (Sheets, Drive, Gmail), Hunter, Google AI Studio | Google (Sheets, Drive, Gmail), Google AI Studio |
| Workflow file | Referral Engine JSON | Job Search JSON, or one click from n8n template 9602 |
| Sheet template | email automation n8n | Job Search N8N |
Both posts are free. All facts about the files below come from reading the published JSON on Oct 4, 2026.
Before you start¶
- A Google account you will send from (personal Gmail or university Google account).
- Your resume as a text-based PDF (exported from Overleaf or Word, not a scan). Extract From File reads text, not images.
- A second copy of that PDF without your phone number and home address, for the AI steps (see the privacy warning in step 4).
- A Hunter account (Referral Engine only).
- Three target companies, each with its email domain (
stripe.com), one job link, and the exact job title. - Two hours for the first setup.
Step 1: Choose where n8n runs¶
| Option | Cost (as of Oct 2026) | Google login | Runs with laptop closed | Best for |
|---|---|---|---|---|
| n8n Cloud trial | Free for 14 days, 1,000 executions, no card. Then Starter at EUR 20 a month billed yearly (2,500 executions; n8n says yearly billing saves 17%) | One click ("Sign in with Google") | Yes | Fastest start |
| Self-host with Docker | Free (Community edition; Docker Personal is $0) | You create your own Google OAuth app (20 minutes) | No, the machine must be awake at run time | No monthly cost |
npx n8n |
Free | Same as Docker | No | Not recommended in Oct 2026 |
One execution is one full run of a workflow, however many steps it has, so a daily schedule uses about 30 a month (n8n pricing). Personal and learning use of the self-hosted version is allowed under n8n's license (license FAQ).
Option A: n8n Cloud¶
- Start the trial from n8n pricing (trial details).
- Note your instance URL (
https://[name].app.n8n.cloud). - Put a calendar reminder on day 12. If you do not upgrade, the trial expires and n8n deletes the workspace.
- Before day 14, export your workflows. After the trial ends you still have 90 days to download them from the Admin Dashboard (download workflows).
Option B: Self-host with Docker¶
- Install Docker Desktop (get Docker) and open it once.
-
Run the one-line installer from n8n (one-line setup). It needs Docker with the
docker composev2 plugin and creates ann8nfolder. -
Open
http://localhost:5678and create the owner account. - Optional: in Settings, open Usage and plan and register for the free license key. It adds folders and debugging in the editor.
Prefer a single container? This is the Docker install command. Set both time zone variables to yours (for example America/New_York, America/Los_Angeles, Asia/Kolkata, Europe/London), or the 5 AM schedule fires at New York time.
docker volume create n8n_data
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-e GENERIC_TIMEZONE="Asia/Kolkata" \
-e TZ="Asia/Kolkata" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-v n8n_data:/home/node/.n8n \
n8nio/n8n
Option C: npx (legacy)¶
Watch out: As of Oct 4, 2026, n8n 3.0 is scheduled for October 2026 and will not support installs run with npm or npx (3.0 breaking changes). Use Docker unless you already have npx running.
Step 2: Copy the two Google Sheets¶
- Open each template and choose File, then Make a copy. Keep the tab names.
- Keep your copies private. The post suggests "anyone with the link can edit", but n8n signs in with your own Google account, so you do not need it. A public editable sheet leaks your contact list and lets anyone add rows that trigger emails.
- Do not rename any column. The workflows write to these exact names, typos included.
- Upload your resume PDF to Google Drive. Private is fine; the Drive node downloads it with your own credential.
- Fill one input row using the tables below.
Referral Engine sheet:
| Tab | Columns (exact) | Example row | Notes |
|---|---|---|---|
Sheet1 (input) |
Company Name, URL, Position |
stripe.com, [link to the exact job], Software Engineer, Backend |
Company Name must be the email domain. "Stripe" finds nothing; stripe.com works. The post lists the order as Company Name, Position, URL; order does not matter, names do |
Results (output) |
Name, Email, Company, Subject, Email Body |
Filled by the workflow | Add a Sent column at the end (step 9 of the Referral Engine fixes) |
Job Search sheet:
| Tab | Columns (exact) | Example row | Notes |
|---|---|---|---|
Filter (input) |
Keyword, Location, Experience Level, Remote, Easy Apply |
Software Engineer Intern, United States, Internship, Hybrid, (blank) |
Only the first row is used |
Result (output) |
Title, Company, Locaton, Link, Score, Cover Letter, Skills, Improvements |
Filled by the workflow | Company has a trailing space and Locaton is misspelled in the template. Leave both as they are |
Step 3: Import the workflows¶
- Download both JSON files from the links in the table at the top.
- In n8n, create a new workflow, open the three-dots menu at the top right, and choose Import from File (import docs). Or open the JSON in a text editor, copy everything, click the empty canvas, and paste with Ctrl+V or Cmd+V.
- For the Job Search Workflow you can instead open template 9602, click "Use for free", and pick your instance.
- Expect red warning icons on the Google, Gemini, Hunter, and Gmail nodes until you attach your own credentials.
Step 4: Connect credentials¶
Google on n8n Cloud (2 minutes)¶
- Open any Google Sheets node, then Credential, then Create new.
- Click "Sign in with Google" and accept. This is n8n's managed OAuth; no Google Cloud setup needed (Google credential docs).
- Repeat for Google Drive and Gmail.
Google on self-hosted n8n (about 20 minutes)¶
- Go to Google Cloud Console and create a project.
- In APIs and Services, Library, enable Google Drive API, Google Sheets API, and Gmail API (enable APIs). The Sheets node also needs the Drive API.
- Open the OAuth consent screen. Set Audience to External, fill the app name and your email, and create it.
- On the Audience page, add your own Gmail address under Test users (manage app audience).
- In n8n, create the Google credential and copy the "OAuth Redirect URL" it shows. On a local install it is
http://localhost:5678/rest/oauth2-credential/callback. - In Google Cloud, go to Credentials, Create credentials, OAuth client ID, type Web application. Paste the redirect URL into Authorized redirect URIs, exactly.
- Copy the Client ID and Client Secret into the n8n credential. Click Sign in with Google.
- Google warns that the app is unverified. That is expected for your own app (unverified apps). Continue, then Save.
- Repeat sign-in for the Drive, Sheets, and Gmail credentials (same client ID and secret).
| Error | Cause | Fix |
|---|---|---|
redirect_uri_mismatch |
The URI in Google is not identical to n8n's (http vs https, port, path) | Copy it again from the n8n credential panel |
| Access denied | Your email is not a Test user | Add it on the Audience page |
invalid_client |
Client ID or secret copied wrong | Paste both again |
| Works, then fails a week later | Apps in Testing get refresh tokens that expire in 7 days (Google OAuth) | Reconnect weekly, or use n8n Cloud's managed OAuth |
Watch out: n8n's Gmail credential asks for full mailbox access. Never share your Client Secret, and never commit it to GitHub.
Gemini API key (Google AI Studio)¶
- Open Google AI Studio API keys and click Create API key (new project). Copy it.
- In n8n, create a "Google Gemini(PaLM) Api" credential and paste the key. Leave the host as
https://generativelanguage.googleapis.com(Gemini credential docs). - Open every "Google Gemini Chat Model" node and pick a model from the dropdown. Imported nodes fall back to
gemini-2.5-flash, which Google now limits to people who used it before (deprecations). A Gemini node you add yourself defaults to a preview model, so change that too. - Choose
gemini-3.5-flash-lite(cheapest) orgemini-3.8-flash(stronger). Google names these two for new projects, and both show "Free of charge" in the free tier column as of Oct 2026 (pricing). - Check your live limits at AI Studio rate limits. The Job Search Workflow makes 2 Gemini calls per job.
- On n8n Cloud you can skip the key and use n8n's Gateway credits during the trial (gateway credits).
Watch out: On the free tier, Google may use your prompts to improve its products, and human reviewers may read them; Google's terms say not to send personal information (Gemini API terms). Users in the EEA, Switzerland, and the UK get the paid-tier data terms even on free use. Both workflows send your resume text, and the Referral Engine sends recruiters' names, so feed the AI nodes the redacted PDF.
Hunter API key (Referral Engine only)¶
- Sign up at Hunter.
- Open API keys, create a new key, and copy it.
- In the Hunter node, create a credential and paste the key (Hunter credential docs).
- Know the budget: the free plan is 50 credits a month, 1 credit per email found, and at most 10 results per search (Hunter pricing, API docs). At 10 contacts per company that is about 5 companies a month. At 3 contacts, about 16.
Workflow 1: The Referral Engine¶
How data flows¶
Execute workflow
-> Get row(s) in sheet (Sheet1) -> Fetch Companies -> Split Out1 -> Loop Over Items
loop: Hunter -> Fetch Data -> Download file1 -> Extract from File
-> AI Agent (+ Google Gemini Chat Model) -> Edit Fields
-> Append or update row in sheet (Results) -> back to Loop Over Items
done: Date & Time -> Merge -> Get row(s) in sheet1 (Results)
-> Remove Duplicates -> Loop Over Items1 -> Send a message (Gmail)
Node by node¶
| # | Node | What it does | What you set |
|---|---|---|---|
| 1 | When clicking 'Execute workflow' | Starts the run when you click | Nothing |
| 2 | Get row(s) in sheet | Reads every row of Sheet1 |
Your sheet copy, tab Sheet1 |
| 3 | Fetch Companies | Copies Company Name into a field called Company |
Nothing |
| 4 | Split Out1 | One item per company | Nothing |
| 5 | Loop Over Items | Processes companies one at a time | Nothing |
| 6 | Hunter | Domain Search on the company domain; returns up to 10 people in departments hr and it |
Credential; lower Limit to 3; keep hr, keep it only if you want engineers |
| 7 | Fetch Data | Builds Name, Email, Position, LinkedIn from Hunter's result |
Nothing |
| 8 | Download file1 | Downloads your resume PDF from Drive, once per contact | Paste your redacted resume's Drive URL |
| 9 | Extract from File | Turns the PDF into text | Add option Keep Source = JSON (fix 4 below) |
| 10 | AI Agent + Google Gemini Chat Model | Writes a subject and body as JSON | Credential, model, and the prompt patch below |
| 11 | Edit Fields | Strips code fences and parses the JSON; writes blanks plus an error field if parsing fails |
Nothing |
| 12 | Append or update row in sheet | Writes Name, Email, Company, Subject, Email Body to Results |
Your sheet copy, tab Results; match on Email |
| 13 | Date & Time, Merge | Pass-through that starts the send branch once the loop is done | Nothing |
| 14 | Get row(s) in sheet1 | Reads all rows of Results |
Your sheet copy, tab Results |
| 15 | Remove Duplicates | Drops repeated emails within this run (docs) | Switch mode to "Remove Items Processed in Previous Executions" |
| 16 | Loop Over Items1 | Sends one email at a time | Nothing |
| 17 | Send a message (Gmail) | Sends to {{ $json.Email }} with Subject and Email Body |
Replace with a draft step (below) |
Jugal's tip on the dedupe node: "Keep it. It saves your domain reputation."
Change these settings after import¶
Found by reading the published JSON against n8n's documentation. They are not visible in the post, so check each one.
- Point every Sheets node at your copy. All three Sheets nodes point at Jugal's private copy.
- Lower the Hunter limit. Set Limit to 3. Ten near-identical emails to one company look like spam, and Hunter's data favors contacting 1 to 2 people per company (Hunter 2026). On the free plan the limit cannot go above 10.
- Add your resume. Paste the Drive URL into Download file1.
- Pass the contact's name to the AI. Extract from File outputs only the PDF text, so
{{ $json.Name }}and{{ $json.Position }}reach the prompt blank and the model may invent a name. Fix: in Extract from File, Add option, Keep Source, JSON. Or change the two expressions to{{ $('Fetch Data').item.json.Name }}and{{ $('Fetch Data').item.json.Position }}. - Stop invented personalization. The prompt asks for "public signals" (a post, talk, repo) but the workflow never supplies any. Append the patch lines below.
- Match on Email, not Name. In "Append or update row in sheet", set the column to match on to
Email, so two people with the same name do not overwrite each other. - Give the send branch a file. The resume is downloaded only in the drafting branch, so the Gmail step has no attachment. Add a Google Drive node (Download, File By URL, your resume) between Loop Over Items1 and the Gmail node. It outputs the file as binary
data. - Finish the Gmail fields. Type your LinkedIn URL after "LinkedIn Profile: ", set the attachment field to
data, and leave "Append n8n attribution" off (Gmail message docs). - Never email the same person twice. The send branch reads every row of
Resultson every run. Add aSentcolumn, put a Filter node after Get row(s) in sheet1 that keeps rows whereSentis empty, and after Gmail add a Google Sheets "Update Row" step that writes today's date toSent, matching onEmail. - Review before send. Replace the Gmail send with a draft (next section).
The AI Agent prompt (verbatim)¶
Copied from the workflow file. En dashes were changed to hyphens for this site; the typos and the broken \email: fragment are in the original and do not stop it from working. The leading = is how the file marks Expression mode: if you paste the prompt into the editor yourself, set the field to Expression and leave the = out.
=Person name- {{ $json.Name }}
Person position- {{ $json.Position }}
The position I am applying for - {{ $('Get row(s) in sheet').item.json.Position }}, make sure the subject includes this role if adding, otherwise it can just be in the body, it should not be any other postion
This is the text from my resume: {{ $json.text }}
company - {{ $('Get row(s) in sheet').item.json['Company Name'] }}
Position seeking referral for link - {{ $('Get row(s) in sheet').item.json.URL }}
"output_schema": {
"subject": "string",
"email_body": "string",
"anchor_topics": [
"string"
]
},
"prompt": "You are a precise cold-outreach writer. Using the provided inputs, generate a crisp subject and a concise referral email for the specified role.\n\nGoals:\n1) Personalize to the recipient by referencing their role, company context, and one concrete anchor topic from recent work or public signals.\n2) Show 1-2 quantified proof points from my resume that align with the job requirements.\n3) Ask for a referral for the specific role, including the job title and link, while keeping the message brief and easy to say yes to.\n\nHard constraints:\n- Subject: 3-7 words, no emojis, no ALL CAPS.\email:, 2-3 short paragraphs, no bullets, no bold, no links except the single job_link provided.\n- Tone: professional; avoid fluff, clichés, and generic praise. No markdown.\n- Personalization must mention exactly one or two anchor topics derived from public signals (post, talk, repo, product launch) or, if none, a shared stack or domain from job_requirements.\n- Close with a clear referral ask and one lightweight next step.\n\nMethod:\n1) From job_requirements, extract 3-5 must-have skills and the role’s core outcomes.\n2) From my_resume, select 1-2 achievements with numbers that map directly to those outcomes.\n3) From public_signals_about_contact and company_domain_industry, derive 1-2 anchor topics (e.g., \"your post on X\", \"the Y launch\", \"open-source Z\"). If none, use a credible fallback like shared tech stack or domain problem.\n4) Draft:\n - Subject: action + outcome or relevance, 3-7 words.\n - Body paragraph 1 (hook): greet by name, reference the anchor topic concisely, tie to the role.\n - Body paragraph 2 (proof + ask): 1-2 quantified resume wins aligned to JD, then ask: \"If it seems like a fit, would you be open to referring me for [job_title]? Here’s the link: [job_link]. Happy to share a 3-5 line summary or code sample.\"\n5) Keep sentences short, concrete, and skimmable. Prefer verbs like \"shipped, scaled, reduced, improved\".\n\nFormatting rules:\n- Return JSON only in the exact output_schema.\n- \"subject\" is a single line.\n- \"email_body\" uses \\n for paragraph breaks; no extra line at end; no markdown; no quotes outside normal punctuation.\n- \"anchor_topics\" is an array of 1-2 short phrases, each <= 80 characters, single line.\n\nQuality checks (must pass before returning):\n- Subject <= 7 words.\n.\n- Contains recipient name and company.\n- References at least 1 anchor topic.\n- Includes a direct referral ask mentioning job_title and job_link.\n\nReturn exactly the following keys: subject, email_body, anchor_topics."
Make sure the email body is atleast 3 paragraphs long + the CTA below
Every email should have a CTA that I am attaching my resume and my Linkedin Profile
Append these lines at the end of the prompt (this site's patch, not in the original):
Never invent posts, talks, repos, launches, or mutual connections. No public signals are provided, so use only the shared tech stack or domain from the job link.
Keep the body to 3 short paragraphs and under 150 words. End with: "If this isn't the right person, no worries, I won't follow up."
The send-block the prompt is built around, from the post:
If it seems like a fit, would you be open to referring me for {{Position}}? Here's the link: {{URL}}. I'm attaching my resume and my LinkedIn for a quick skim.
First test run¶
- Put exactly 1 row in
Sheet1. - Select the Gmail node and deactivate it, so nothing can send.
- Click Execute workflow. Watch each node turn green.
- Open
Results. Check that Name and Email are filled and that the body uses the real first name. - Read every email. Delete any line that claims something untrue about the person or about you.
- If a Subject or Email Body is blank, open the AI Agent output for that item. The model returned something that was not JSON; lower the temperature on the Gemini node or try the stronger model.
- Repeat with 3 rows. Only then move on.
Review before send: Gmail drafts¶
- Delete the "Send a message" node, or deactivate it.
- Add a Gmail node: Resource Draft, Operation Create (draft docs).
- Subject:
{{ $json.Subject }}. Message:{{ $json['Email Body'] }}. - Options: To Email
{{ $json.Email }}; Attachments fielddata(from the Drive download you added in fix 7). - Run the workflow. Open Gmail, then Drafts.
- Each morning: edit the first line of each draft so it is about that person, then send 10 to 15. That is Jugal's daily pace (The Job Hunt I Didn't Burn Out Doing).
- Log each sent email in your tracker with a follow-up date.
Schedule it (optional)¶
- Only after three clean manual runs.
- Replace the manual trigger with a Schedule Trigger. Jugal's post suggests daily at 17:00 America/Phoenix; use your own time zone.
- Set the time zone in the workflow settings (workflow settings).
- Click Publish. In n8n 2.x, Publish replaced the old Activate toggle that the 2025 posts mention (2.0 breaking changes, save and publish).
- Each morning, open Gmail Drafts and work through the review steps above. The schedule only drafts; you still send by hand.
Workflow 2: The Job Search Workflow¶
How data flows¶
Schedule Trigger (5 AM)
-> Download file (resume) -> Extract from File -> Get row(s) in sheet (Filter)
-> LinkedIn Search URL (Code) -> Fetch jobs from LinkedIn -> HTML -> Split Out -> Loop Over Items
loop: Wait (2 s) -> HTTP Request (job page) -> HTML1 -> Edit Fields
-> AI Agent (+ Gemini): match score and cover letter -> Edit Fields1 (parse JSON)
-> AI Agent1 (+ Gemini): resume edits
-> Append or update row in sheet1 (Result) -> back to Loop Over Items
done: Send a message (Gmail: "results are ready")
Node by node¶
| # | Node | What it does | What you set |
|---|---|---|---|
| 1 | Schedule Trigger | Runs daily at 5 AM in the workflow's time zone | Hour, and the time zone in workflow settings |
| 2 | Download file | Downloads your resume PDF | Paste your redacted resume's Drive URL |
| 3 | Extract from File | PDF to text, used by both AI steps | Nothing |
| 4 | Get row(s) in sheet | Reads the Filter tab |
Your sheet copy, tab Filter |
| 5 | LinkedIn Search URL (Code) | Builds a LinkedIn search URL for jobs posted in the last 24 hours from the first filter row | Nothing (values in the table below) |
| 6 | Fetch jobs from LinkedIn | Downloads the public search page | Nothing |
| 7 | HTML | Pulls each job link from the page | Fix the CSS selector here if LinkedIn changes its HTML |
| 8 | Split Out | One item per job link | Nothing |
| 9 | Loop Over Items | One job at a time | Nothing |
| 10 | Wait | Pauses 2 seconds before each job page | Raise to 10 seconds if you hit rate limits |
| 11 | HTTP Request + HTML1 | Downloads the job page and extracts Title, Company, Location, Description, Job ID | Nothing |
| 12 | Edit Fields | Cleans the description and builds the apply link | Nothing |
| 13 | AI Agent + Google Gemini Chat Model | Match score 0 to 100, score breakdown, gaps, 150 to 220 word cover letter, as JSON | Credential and model |
| 14 | Edit Fields1 | Parses the JSON (strips fences, fixes curly quotes, stops at END_OF_JSON) |
Nothing |
| 15 | AI Agent1 + Google Gemini Chat Model1 | Numbered list of tagged resume edits | Credential, model, and fix 4 below |
| 16 | Append or update row in sheet1 | Writes one row per job to Result, matched on Link |
Your sheet copy, tab Result |
| 17 | Send a message | Emails you when the loop finishes | Replace <your e-mail address> with yours |
Filter values¶
The Code node maps these exact strings to LinkedIn's filters. Anything else is silently ignored.
| Column | Accepted values | Example | Notes |
|---|---|---|---|
Keyword |
Plain words | Software Engineer Intern |
Not URL-encoded, so avoid &, #, + |
Location |
A place LinkedIn understands | United States, India, London |
|
Experience Level |
Internship, Entry level, New Grad (comma-separated for several) |
Internship |
Case-sensitive. The template's sample Entry Level (capital L) does not match, so the level filter is silently skipped |
Remote |
Remote, Hybrid, On-Site (comma-separated) |
Hybrid,Remote |
|
Easy Apply |
Any text turns it on; blank turns it off | (blank) |
Only the first row is read. Jugal's rule: for more searches, make a copy of both the sheet and the workflow.
Change these settings after import¶
- Add your resume. Paste the Drive URL into Download file.
- Point both Sheets nodes at your copy. Tabs
FilterandResult. - Pick models. Both Gemini nodes need your credential and a current model (Step 4). Optional: temperature 0.2 to 0.4 keeps the JSON stable.
- Give the resume editor its inputs. AI Agent1's prompt is saved as fixed text, so Gemini receives the literal
{{ }}placeholders and writes Improvements without seeing the job or your resume. Fix: open AI Agent1, switch the Prompt field from Fixed to Expression, and replace{{ $json.Description }}with{{ $('Edit Fields').item.json.Description }}. The gallery copy (template 9602) has the same setting. - Set your email. In Send a message, replace
<your e-mail address>. - Set the time zone. Workflow settings, Timezone. Self-hosted n8n defaults to America/New_York.
The two prompts (verbatim)¶
Job matching (node "AI Agent"). En dashes changed to hyphens for this site.
=You are a precise job-matching assistant.
Return ONE JSON object wrapped in ```json fences, followed by the line END_OF_JSON.
No extra prose. No Markdown inside the JSON. No comments.
INPUTS
job_description: {{ $json.Description }}
my_resume: {{ $('Extract from File').item.json.text }}
TASKS
1) Parse job_description → job_analysis with keys:
title (string), company (string), must_have_skills (string[]), nice_to_have_skills (string[]),
responsibilities (string[]), years_of_experience (string), education_certifications (string),
location_constraints (string), domain_industry_focus (string), tech_stack (string[]), measurable_kpis (string[])
2) Parse my_resume → resume_analysis:
core_skills (string[]),
tools_tech { programming_languages[], frontend_technologies[], backend_technologies[], databases_devops[] },
years_of_experience_key_areas (object of short strings),
accomplishments_with_metrics (string[]),
education_certs (string[]), domains (string[]), roles_titles (string[]),
leadership_collaboration (string[]), location_work_auth (string)
3) Scoring (integer 0-100):
- Skills/Tools overlap: 40
- Relevant experience & seniority: 25
- Responsibilities alignment: 15
- Education/Certs fit: 10
- Domain/industry fit: 5
- Logistics (location/work auth/availability): 5
Allow partial credit; deduct up to 10 via red_flags. Clamp to [0,100], integer.
4) Explain the score:
For each bucket, provide 1-3 concise evidence bullets. Cite "JD" or "Resume" and include short quoted fragments (escape quotes).
5) Gaps & Suggestions:
List missing/weak requirements with 1-2 concrete upskilling steps per gap.
6) Cover letter:
150-220 words (2-4 short paragraphs), tailored to the role/company.
Concrete impacts; no greeting/signature. JSON-safe: escape all " as \", use \n for newlines.
STRICT CONTENT RULES (to prevent invalid JSON)
- Do NOT paste raw paragraphs, markdown (**bold**, lists), headings, or multi-line blocks into any array fields.
- Every array element must be a short phrase (≤ 140 characters), single line, no line breaks, no asterisks, no bullets.
- If a JD section is long, summarize into short phrases before placing into arrays.
- Do NOT include unrelated job text inside arrays or objects. Keep each value semantically atomic.
- Never invent company/title; use "" if unknown.
- No trailing commas anywhere.
STRICT OUTPUT RULES
- Output exactly the following schema (keys and types). No extra keys.
SCHEMA
```json
{
"job_analysis": {
"title": "",
"company": "",
"must_have_skills": [],
"nice_to_have_skills": [],
"responsibilities": [],
"years_of_experience": "",
"education_certifications": "",
"location_constraints": "",
"domain_industry_focus": "",
"tech_stack": [],
"measurable_kpis": []
},
"resume_analysis": {
"core_skills": [],
"tools_tech": {
"programming_languages": [],
"frontend_technologies": [],
"backend_technologies": [],
"databases_devops": []
},
"years_of_experience_key_areas": {},
"accomplishments_with_metrics": [],
"education_certs": [],
"domains": [],
"roles_titles": [],
"leadership_collaboration": [],
"location_work_auth": ""
},
"match_score": 0,
"score_explanation": [
{ "category": "Skills/Tools overlap (40 points)", "score": 0, "evidence": [] },
{ "category": "Relevant experience depth & seniority (25 points)", "score": 0, "evidence": [] },
{ "category": "Responsibilities alignment (15 points)", "score": 0, "evidence": [] },
{ "category": "Education/Certs fit (10 points)", "score": 0, "evidence": [] },
{ "category": "Domain/industry fit (5 points)", "score": 0, "evidence": [] },
{ "category": "Logistics (location, work auth, availability) (5 points)", "score": 0, "evidence": [] }
],
"red_flags": [],
"gaps_and_suggestions": [
{ "gap": "", "suggestion": "" }
],
"cover_letter": ""
}
Resume editor (node "AI Agent1"), shown with fix 4 applied. The original first input was {{ $json.Description }}. Set the field to Expression before pasting.
You are a ruthless resume editor. Compare the inputs and output ONLY crisp, point-wise changes to improve job fit.
Inputs:
- job_description: {{ $('Edit Fields').item.json.Description }}
- my_resume: {{ $('Extract from File').item.json.text }}
Instructions:
- Output a numbered list; highest-impact first.
- One line per point; <= 14 words.
- Start each line with a tag: [ADD], [REMOVE], [REWRITE], [ORDER], [QUANTIFY], [KEYWORDS], [FORMAT], [FOCUS].
- Base every point on gaps vs. the job_description; do not invent experience.
- Prefer concrete actions: skills to add, bullets to rewrite, sections to reorder/remove.
- Include one line: 'Missing keywords: term1, term2, ...' (only if any).
- No intros, explanations, code fences, or extra text - points only.
Output: points only, exactly as specified above.
Tip: Want to delete the hand-written JSON parser? The AI Agent has a "Require Specific Output Format" option that attaches a Structured Output Parser. Paste the schema above as its JSON example.
First test run¶
- Add a Limit node between Split Out and Loop Over Items, with Max Items 3.
- Click Execute workflow.
- Open
Result. Each row should have a Score, a Cover Letter, Skills, and an Improvements list that names things from that job. - If Gemini returns a 429 ("too many requests"): open the AI Agent node Settings, turn on Retry On Fail, Max Tries 5, Wait Between Tries 5000 ms (the highest the editor allows). For longer back-off, raise the Wait node from 2 to 10 seconds (rate limits).
- Run it two more mornings by hand. When all three runs fill
Resultcleanly, remove the Limit node.
Schedule it¶
- Open Schedule Trigger and set the hour. The file ships with 5 AM.
- Open the workflow Settings and set Timezone to yours. Self-hosted n8n otherwise uses America/New_York (workflow settings).
- Click Publish. A schedule runs only on a published workflow (Schedule Trigger).
- Self-hosted: keep the machine awake at that hour, or move to n8n Cloud.
- Attach the error alert from Make failures loud.
Use the output every morning¶
- Sort
Resultby Score, highest first. - Open the top 5 links. Drop any that fail your sponsorship or location check.
- For each, apply only the Improvements that are true for you. The prompt itself says "do not invent experience." See resume tailoring.
- Treat the Cover Letter as a draft. Rewrite the first two lines in your own words.
- Apply, then log the application and set a follow-up date in your tracker.
- Pair the best 2 or 3 with a referral ask from the Referral Engine.
Internship mode without scraping LinkedIn¶
Watch out: LinkedIn's User Agreement prohibits scraping its services with scripts or bots (User Agreement). Jugal's post says to be mindful of this. Run Workflow 2 at most once a day, never with your logged-in session, or switch the source below.
Summer internships are reviewed on a rolling basis, so the first week after a posting matters more than the deadline (494 Summer 2027 Internships Are Already Live). A safer daily source is the data file behind the SimplifyJobs list:
- Replace "Fetch jobs from LinkedIn", "HTML", and the per-job "HTTP Request" with one HTTP Request node: GET
https://raw.githubusercontent.com/SimplifyJobs/Summer2027-Internships/dev/.github/scripts/listings.json(about 13 MB). New grads: usehttps://raw.githubusercontent.com/SimplifyJobs/New-Grad-Positions/dev/.github/scripts/listings.json; the same code works. - Add a Code node in "Run Once for All Items" mode with the filter below. If the HTTP node gives you one item holding the whole array, add a Split Out node first.
- These listings have no job description, so either skip the AI scoring or fetch each company's job page before the AI Agent.
// n8n Code node, mode "Run Once for All Items".
// Keeps active roles posted in the last 24 hours in the chosen categories.
const listings = $input.all().map(i => i.json);
const HOURS = 24;
const CATEGORIES = ['Software', 'AI/ML/Data', 'Quant'];
const BLOCKED = ['Does Not Offer Sponsorship', 'U.S. Citizenship is Required']; // international students
const cutoff = Math.floor(Date.now() / 1000) - HOURS * 3600; // date_posted is Unix seconds
return listings
.filter(j => j.active && j.is_visible)
.filter(j => j.date_posted >= cutoff)
.filter(j => CATEGORIES.includes(j.category))
.filter(j => !BLOCKED.includes(j.sponsorship))
.map(j => ({ json: {
Company: j.company_name,
Title: j.title,
Location: (j.locations || []).join('; '),
Link: j.url,
Posted: new Date(j.date_posted * 1000).toISOString().slice(0, 10),
Sponsorship: j.sponsorship,
}}));
This filter was tested outside n8n on Oct 4, 2026 data. Most listings mark sponsorship as "Other" (not stated), so international students still need to check each posting; see international students.
For specific companies, use their official public job APIs in an HTTP Request node:
| ATS | Endpoint | Docs |
|---|---|---|
| Greenhouse | https://boards-api.greenhouse.io/v1/boards/[token]/jobs?content=true (content=true adds descriptions) |
Greenhouse Job Board API |
| Lever | https://api.lever.co/v0/postings/[company]?mode=json |
Lever Postings API |
| Ashby | https://api.ashbyhq.com/posting-api/job-board/[company] |
Ashby public job posting API |
More job sources: where to find jobs and finding internships.
Safety and sending limits¶
| Risk | Limit or rule | What to do |
|---|---|---|
| Gmail blocks you | Personal Gmail: more than 500 emails a day (Google). Workspace: 2,000 a day (Google) | Stay at 10 to 15 a day. That is 2 to 3% of the cap |
| Spam complaints | Every Gmail sender must stay under a 0.3% spam rate (sender guidelines) | Personal line in every email; stop when asked |
| Too many people per company | Hunter's data: 1 to 2 contacts per company replies best | Hunter Limit 3 at most |
| Repeat emails | The send branch rereads every row | Sent column plus Filter, or cross-run dedupe (fix 9) |
| Invented personalization | The prompt asks for signals it never receives | Prompt patch plus read every draft |
| Following up forever | 3 touches per person | See follow-up and tracking |
| Breaking email law | CAN-SPAM requires honest headers and subjects and honoring opt-outs (FTC) | Real name, true subject, an opt-out line. Details in cold email |
| LinkedIn account restriction | Scraping is against the User Agreement | Internship mode or official ATS APIs |
| Your data in AI training | Free Gemini tier may be read by reviewers (outside EEA, CH, UK) | Redacted resume PDF, or a paid key |
| Leaked secrets | Exported workflow JSON contains credential names and IDs (export docs) | Never commit keys; check JSON before sharing |
| Public sheet | Anyone with an edit link can add rows that trigger emails | Keep both sheets private |
Tip: Want the resume to never leave your laptop? On self-hosted n8n, swap the Gemini Chat Model for an Ollama Chat Model running a local model. Small local models follow the long JSON prompt less reliably, so test with 3 jobs.
Make failures loud¶
Expired Google tokens, a LinkedIn HTML change, and Gemini 429s all stop a scheduled run without telling you. Set up one alert workflow for both automations.
- Create a new workflow that starts with an Error Trigger node.
- Add a Gmail node that sends you "Workflow failed" with the workflow name.
- Publish it.
- In each job-search workflow, open Settings, then Error workflow, and pick it (workflow settings).
- Note: error workflows only fire on automatic runs, not when you click Execute.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Sheet writes fail | Column names do not match | Restore the exact headers from Step 2 |
| Hunter returns nothing | Company Name is a name, not a domain |
Use stripe.com; loosen the department filter |
Hunter pagination_error |
Limit above 10 on the free plan | Set Limit to 10 or lower |
| Emails say "Hi there" or use the wrong name | AI Agent is not receiving Name and Position | Fix 4 (Keep Source = JSON) |
| Gmail error about missing binary data | No file in the send branch | Fix 7 (Drive download before Gmail) |
| Same people emailed again | Send branch reads all rows | Fix 9 (Sent column) |
| Blank Subject or Body, or empty Result cells | Model returned non-JSON | Open the AI Agent output; lower temperature; try the stronger model |
| Gemini 404 or model error | Model not available to your key | Pick a current model from the dropdown |
| Gemini 429 | Free-tier rate limit | Retry On Fail, longer Wait, fewer jobs per run, or Flash-Lite |
| Improvements column is vague | AI Agent1 still in Fixed mode | Fix 4 of Workflow 2 |
| Experience filter ignored | Entry Level instead of Entry level |
Use the exact values in the filter table |
| LinkedIn returns nothing or a login wall | Selectors changed or you were rate-limited | Stop; switch to internship mode |
| Google credential dies weekly | OAuth app in Testing (7-day tokens) | Reconnect, or use n8n Cloud |
| Schedule never fires | Not Published, wrong time zone, or the machine was asleep | Publish; set the time zone; keep the host awake |
What it costs¶
| Item | Free option | Paid option (as of Oct 2026) |
|---|---|---|
| n8n | Self-host (Community edition) or the 14-day Cloud trial | Cloud Starter EUR 20 a month billed yearly |
| Hunter | 50 credits a month | Starter $49 a month, or $34 a month billed yearly |
| Gemini | Free tier on gemini-3.5-flash-lite and gemini-3.8-flash |
Pay per token (pricing) |
| Gmail, Drive, Sheets | Free | n/a |
| Docker Desktop | Personal plan $0 (pricing) | n/a |
Resources¶
- n8n docs: official documentation. How to use it: search the node name when a setting here does not match your screen.
- n8n Academy: official courses (free registration). How to use it: take N8N101 Essentials before you edit nodes.
- Jugal's n8n creator page: his gallery templates. How to use it: check for updated versions.
- Hunter node docs: Domain Search, Email Finder, Email Verifier. How to use it: add an Email Verifier step before drafting.
- Gemini Chat Model node docs: model and temperature options. How to use it: set temperature low for JSON.
- Extract From File docs: PDF to text. How to use it: confirm your resume PDF has selectable text.
- GitHub Actions billing: free scheduled runners for public repos. How to use it: only if you rebuild this as a script; keep keys in Actions secrets, never in the repo.
- Cold email: the writing rules the drafts should follow. How to use it: compare each draft against the pre-send checklist.