Resume Screening Automation with n8n

Automate resume screening with a local LLM, Gmail, and Google Sheets.

Introduction

30 Second Summary

Hiring managers spend an average of 6 seconds scanning a single resume. When dozens arrive every day, that adds up to hours of repetitive screening that could be handled by a machine.

In this project, you will build an automated resume screening pipeline using n8n, Ollama, and Google Sheets. Your workflow monitors Gmail for incoming resumes, uses a local AI model to parse and score each candidate, then logs everything to a ranked dashboard.

What You'll Build

A self-hosted n8n workflow with 7 nodes that automatically detects resume emails in Gmail, extracts PDF text, scores candidates using a local LLM, and outputs a ranked Google Sheets dashboard.

By the end of this project, you'll have:

  • A working n8n workflow that automatically detects new resume emails in Gmail and downloads the PDF attachments.
  • An AI-powered parsing and scoring pipeline that evaluates each resume against custom software engineer criteria using a local LLM.
  • A live Google Sheets dashboard with candidates ranked by score, including name, email, skills match, and score breakdown.
  • Secret Mission: Add a notification that alerts you when a candidate scores above 80/100.

Are there any prerequisites?

This project assumes you already have a running n8n instance (self-hosted via Docker), Ollama installed and accessible from n8n, and Google OAuth2 credentials connected in n8n for Gmail, Sheets, and Drive.

If you haven't set these up yet, complete the n8n Docker setup and Google credentials configuration before starting.

Pull the Ollama Model and Prepare Google Sheets

Before you can build the resume screening workflow, you need three things in place: an AI model that can read and understand resumes, a dashboard to collect results, and a folder to archive the original PDFs.

In this step, you'll pull a local LLM into Ollama and set up the Google Sheets and Google Drive resources your workflow will write to.

In this step, get ready to:
  • Pull the qwen2.5:3b AI model into Ollama.
  • Create a Google Sheets dashboard for candidate scores.
  • Create a Google Drive folder for resume storage.
Pull the qwen2.5:3b model

Your workflow will use the qwen2.5:3b model to parse resume text and score candidates. This model runs entirely on your machine, so there are no API costs or data privacy concerns.

  • Download the model by running this command in your terminal:
ollama pull qwen2.5:3b

This downloads approximately 2 GB. It may take a few minutes depending on your internet connection.

  • Confirm the model downloaded successfully by running:
ollama list

You should see qwen2.5:3b listed in the output along with its size (approximately 1.9 GB).

Model not appearing in the list?

Make sure Ollama is running. You can check by running ollama serve in a separate terminal window if it's not already active.

If the pull failed partway through due to a network error, run ollama pull qwen2.5:3b again. Ollama resumes partial downloads automatically.

Help me troubleshoot why my Ollama model isn't pulling correctly.

Why qwen2.5:3b specifically?

Smaller models (like 0.5B parameter models) fail at structured JSON extraction. They hallucinate field values and produce malformed output.

The 3B model is the minimum practical size for reliable structured output from resumes. It runs on modest hardware while still producing consistent, parseable JSON.

Create the Resume Screening Dashboard

Your workflow will log every screened candidate to a Google Sheet. You need to set up the column structure now so the workflow knows where to write each piece of data.

  • Open Google Sheets by navigating to sheets.google.com in your browser.
  • Click **Blank spreadsheet** to create a new sheet.
  • Click on Untitled spreadsheet in the top-left corner and rename it to Resume Screening Dashboard.
  • In row 1, add these column headers starting from cell A1: Score, Name, Email, Key Skills, Experience Years, Match Reasons, Date Received, Subject Line.
  • Select the entire column A by clicking the column letter A at the top of the spreadsheet.
  • Click Format in the menu bar.
  • Select Number, then Number from the submenu to format the Score column as a number.

Your spreadsheet should now have 8 column headers across row 1, with the Score column formatted as a number type.

Headers not fitting in the cells?

Some headers like Experience Years may appear truncated. You can widen columns by dragging the column border in the header row, but this is purely cosmetic. The workflow will write data correctly regardless of column width.

Create the Resumes folder in Google Drive

The workflow will upload each resume PDF to Google Drive for future reference. You need a dedicated folder for these uploads.

  • Open Google Drive by navigating to drive.google.com in your browser.
  • Click New in the top-left corner.
  • Select New folder.
  • Name the folder Resumes and click Create.

You should see the empty Resumes folder appear in your Google Drive.

Your AI model is ready and your output destinations are set up. Next up, you'll build the n8n workflow that monitors Gmail for incoming resumes and downloads their PDF attachments.

Set Up the Gmail Trigger and Attachment Download

Your Ollama model is ready and your Google Sheet is waiting for data. Now it's time to build the actual workflow that detects incoming resumes.

Every automation starts with a trigger. In this step, you'll configure n8n to watch your Gmail inbox for new emails with PDF attachments, then download those attachments so the rest of the pipeline can process them.

In this step, get ready to:
  • Create a new n8n workflow with a Gmail Trigger node that watches for PDF attachments.
  • Add a Gmail Get node that downloads the full email and its binary attachment data.
  • Test the workflow end-to-end with a sample resume email.
Create a new workflow and add the Gmail Trigger node

The Gmail Trigger node uses polling to check your inbox at regular intervals. It doesn't download attachments on its own. It only detects that a new matching email arrived and passes along the message ID.

  • Open your n8n instance in your browser.
  • Click the + button in the top-right corner to create a new workflow.
  • Click the + icon on the canvas to add your first node.
  • Search for Gmail Trigger and select it.
  • Select your Google OAuth2 credential from the Credential to connect with dropdown.
  • Turn OFF the Simplify toggle. This ensures the node returns the full raw message data including the message ID you'll need in the next node.
  • In the Filters section, find the Search field and enter this value:
has:attachment filename:pdf

What does this search filter do?

This uses Gmail's built-in search syntax. has:attachment limits results to emails with any attachment. filename:pdf further narrows it to only emails containing PDF files.

This means your workflow ignores every email that doesn't have a PDF resume attached.

  • Set the Poll Times section. Select Every X as the mode.
  • Set the value to 5 and the unit to Minutes.
  • Close the node settings panel by clicking outside of it or pressing Escape.

Why polling every 5 minutes?

The Gmail Trigger uses polling (checking at intervals) instead of real-time webhooks. Five minutes gives you near-real-time detection without hammering the Gmail API. Each poll fetches up to 50 new emails.

Add the Gmail Get node for downloading attachments

The trigger only detects new emails. To actually download the PDF file, you need a separate Gmail node that fetches the full message content including binary attachment data.

  • Hover over the right edge of your Gmail Trigger node until you see the + connector. Click it to add a new node after the trigger.
  • Search for Gmail and select the regular Gmail node (not the trigger version).
  • Set the Resource to Message.
  • Set the Operation to Get.
  • In the Message ID field, click the Expression tab (the small toggle next to the field). Paste this expression:
{{ $json.id }}

What does this expression do?

The {{ $json.id }} expression pulls the message ID from the Gmail Trigger's output. This tells the Gmail Get node exactly which email to fetch the full content for.

  • Scroll down to the Options section and click Add Option.
  • Select Download Attachments from the dropdown.
  • Toggle Download Attachments to ON.
  • Rename this node to Gmail Get by double-clicking the node title and typing the new name.

Why a two-node pattern instead of one?

The Gmail Trigger in n8n only detects new messages and returns metadata. It does not download attachment binary data. The separate Gmail Get node with Download Attachments enabled is what actually fetches the PDF file content.

This two-node pattern (Trigger for detection, Get for data) is standard in n8n whenever you need to process attachment contents.

Test the workflow with a sample resume email

Before building the rest of the pipeline, confirm that the trigger detects your email and that the Get node successfully downloads the PDF as binary data.

  • Send a test email to the Gmail account connected to n8n. Attach any PDF file (a real resume works best for later testing).
  • Back in n8n, click on the Gmail Trigger node to select it.
  • Click Test step (or Fetch Test Event) to manually poll for the email you just sent.
  • Confirm the trigger output shows your test email with an id field in the JSON output panel on the right.
  • Click on the Gmail Get node to select it.
  • Click Test step to execute just this node.
  • In the output panel, switch to the Binary tab. You should see an entry called attachment_0 with your PDF file name and MIME type application/pdf.

Don't see attachment_0 in the Binary tab?

Make sure Download Attachments is toggled ON in the Gmail Get node's Options section.

Verify that the test email you sent actually has a PDF file attached (not just a Google Drive link to a PDF).

Check that the Message ID field uses the Expression tab (not the Fixed tab) with the value {{ $json.id }}.

help me troubleshoot why my Gmail Get node isn't showing binary attachment data

What if the binary field has a different name?

In most n8n versions, the first attachment is named attachment_0. If yours shows a different name (like data or attachment_1), note it down. You'll need the exact name in the next step when extracting the PDF text.

Your workflow can now detect resume emails and download the PDF files. Next up, you'll extract the text from those PDFs and feed it to your local AI model for parsing.

Extract PDF Text and Parse with Ollama

Your workflow can now detect resume emails and download the PDF attachment.

The next challenge is turning that raw PDF binary into useful, structured data. In this step, you'll extract the text from the PDF and send it to your local Ollama model for intelligent parsing. The model will return structured JSON with the candidate's name, email, skills, and experience.

In this step, get ready to:
  • Add an Extract From File node to convert the PDF binary into plain text.
  • Add a Basic LLM Chain node connected to Ollama for AI-powered parsing.
  • Configure a Structured Output Parser to get clean JSON from the LLM.
Add the Extract From File node

The Gmail Get node outputs the resume as binary data, which is just raw bytes. You need to convert that binary into readable text before the AI can process it.

  • Click the + button to the right of the Gmail Get node to add a new node.
  • Search for Extract From File and select it.
  • Set the Operation to Extract From PDF.
  • Set the Input Binary Field to attachment_0.

Why attachment_0?

When n8n downloads email attachments, it names them sequentially: attachment_0, attachment_1, and so on.

Since resume emails typically have one PDF attached, attachment_0 targets that first attachment.

  • Click Test step to run the Extract From File node using the data from your previous test.

You should see the node output a text field containing the readable content from your test resume PDF.

Seeing empty text or an error?

If the text field is empty, your test PDF may be a scanned image rather than a text-based PDF. Try a different resume PDF that was created digitally (not scanned).

If you see a "binary field not found" error, double-check that the Input Binary Field is set to exactly attachment_0. You can verify the actual field name by clicking the Gmail Get node and checking its output panel.

Help me fix my Extract From File node

Add the Basic LLM Chain node with Ollama

Now that you have plain text, you need an LLM to intelligently read it and pull out specific fields like name, email, and skills.

  • Click the + button to the right of the Extract From File node.
  • Search for Basic LLM Chain and select it.
  • Rename this node to Parse Resume by double-clicking its title.

Why Basic LLM Chain instead of AI Agent?

The Ollama Model node does not support tools, so it cannot work with the AI Agent node. The Basic LLM Chain node is the correct choice for connecting Ollama to a structured prompt-and-response workflow.

Next, connect the Ollama Chat Model as a sub-node.

  • Inside the Basic LLM Chain node, click the Chat Model connector (the small circle below the node labeled Model).
  • Search for Ollama Chat Model and select it.
  • Select your Ollama credentials (the ones you configured during setup).
  • In the Model dropdown, select qwen2.5:3b.
  • Under Options, set Temperature to 0.1.

Why set the temperature so low?

Temperature controls how "creative" the model's responses are. A low temperature like 0.1 makes the model produce consistent, predictable outputs.

For data extraction, you want the model to faithfully report what it sees in the resume, not get creative with its interpretation.

Configure the Structured Output Parser and prompt

The LLM will return free-form text by default. A Structured Output Parser forces the model to return data in a specific JSON shape that downstream nodes can reliably use.

  • Back on the Basic LLM Chain (Parse Resume) node, toggle Require Specific Output Format to ON.
  • Click the Output Parser connector that appears.
  • Search for Structured Output Parser and select it.
  • Set Schema Type to Generate From JSON Example.
  • Paste the following JSON into the JSON Example field:
{
  "name": "John Doe",
  "email": "john@example.com",
  "skills": ["Python", "React"],
  "experience_years": 5,
  "education": "BS Computer Science",
  "summary": "Full-stack developer with cloud experience"
}

What does this JSON example do?

The Structured Output Parser uses this example to learn what fields to expect and their data types. It will instruct the LLM to return data matching this exact shape for every resume.

All fields are treated as mandatory. If the model cannot find a value in the resume, your prompt will tell it to use null.

Now set up the prompt that tells the model what to extract.

  • Back on the Parse Resume node, set the Prompt field to Define below.
  • Paste this prompt text into the text area:
Extract the following information from this resume text. If a field is not found, use null.

Resume text:
{{ $json.text }}

What does this prompt do?

The prompt gives the model a clear instruction: extract specific fields from the text. The {{ $json.text }} expression dynamically inserts the extracted PDF text from the previous node.

Combined with the Structured Output Parser, the model knows both what to extract and what format to return it in.

  • Click Test step on the Parse Resume node to run it with your sample resume data.

You should see structured JSON output with the candidate's name, email, skills array, experience years, education, and summary populated from your test resume.

Give the model a moment

Since qwen2.5:3b runs locally, the first request may take 10-30 seconds while the model loads into memory. Subsequent requests will be faster.

Getting malformed output or a timeout?

If the output is not valid JSON, confirm your Structured Output Parser has the JSON example pasted exactly as shown above. A missing bracket or quote will break the schema.

If the node times out, check that Ollama is still running. Open your terminal and run ollama list to confirm qwen2.5:3b is available.

If you see a connection error, verify your Ollama credentials URL in n8n. For Docker Desktop on Mac, use http://host.docker.internal:11434. For containers on the same Docker network, use the Ollama container name as the host.

Help me debug my Parse Resume node

Your workflow can now extract candidate details from any resume PDF. Next, you'll build the scoring engine that evaluates each candidate against your software engineer criteria.

Score the Candidate with a Second LLM Chain

Your workflow can now extract structured candidate data from a raw PDF. That's useful, but it doesn't tell you how good a candidate is for your role.

In this step, you'll add a second Basic LLM Chain node that takes the parsed candidate data and scores it against a weighted rubric for a Software Engineer role. The score, matching skills, and missing skills all come back as structured JSON.

In this step, get ready to:
  • Add a second Basic LLM Chain node with its own Ollama model and Structured Output Parser.
  • Write a scoring prompt that evaluates candidates on 6 weighted criteria.
  • Test the full chain and confirm a valid JSON score comes back.
Add the Score Candidate node

You'll use a separate LLM Chain node for scoring instead of combining parsing and scoring into one prompt. Small models like qwen2.5:3b perform better when each prompt has a single, focused task.

Why two separate LLM nodes?

The n8n documentation recommends using a separate LLM chain to receive data and parse it because structured output parsing can be unstable with a single combined prompt. Splitting into two focused steps (extract, then score) gives more reliable JSON output from smaller models.

  • In your n8n workflow, click the + button on the right side of the Parse Resume node to add a new node after it.
  • Search for Basic LLM Chain and select it.
  • Rename this node to Score Candidate by double-clicking the node title at the top of the panel.
  • Click Ollama Chat Model in the sub-nodes panel below the prompt area to attach a model.
  • Select your Ollama credentials and set the model to qwen2.5:3b.
  • Set the Temperature to 0.1 for consistent scoring output.
  • Toggle on Require Specific Output Format in the node settings.
  • Click the Structured Output Parser sub-node that appears after enabling the output format option.
Configure the output schema and scoring prompt

The Structured Output Parser needs a JSON example that defines the shape of the scoring response. The node derives field names and types from this example automatically.

  • In the Structured Output Parser, set Schema Type to Generate from JSON Example.
  • Paste the following JSON example into the JSON Example field:
{
  "score": 75,
  "match_reasons": "Strong Python skills, AWS experience, lacks React",
  "skill_matches": ["Python", "AWS", "Docker"],
  "missing_skills": ["React", "CI/CD"]
}

What does this schema define?

  • score is a numeric value out of 100 representing the candidate's overall fit.
  • match_reasons is a short summary explaining what skills matched and what's missing.
  • skill_matches lists the specific skills the candidate has from your requirements.
  • missing_skills lists the required skills the candidate lacks.
  • Close the Structured Output Parser and go back to the main Score Candidate node settings.
  • In the Prompt section, set the type to Define below.
  • Paste the following scoring prompt into the text area:
You are a technical recruiter scoring resumes for a Software Engineer role.

Required skills (weighted):
- Python (20 points)
- JavaScript/React (20 points)
- Cloud (AWS/GCP/Azure) (20 points)
- CI/CD (GitHub Actions, Jenkins) (15 points)
- Docker/Kubernetes (15 points)
- 3+ years experience (10 points)

Candidate info:
Name: {{ $json.output.name }}
Skills: {{ $json.output.skills }}
Experience: {{ $json.output.experience_years }} years
Education: {{ $json.output.education }}
Summary: {{ $json.output.summary }}

Score this candidate out of 100. Be specific about which skills matched and which are missing.

What does this prompt do?

The prompt gives the LLM a clear role (technical recruiter) and a weighted scoring rubric totalling 100 points. Each skill category has an explicit point value so the model produces consistent, justifiable scores.

The {{ $json.output.name }} expressions pull structured fields from the previous Parse Resume node's output. The $json.output path refers to the parsed data that the Structured Output Parser extracted in the previous step.

Test the scoring pipeline

You need to verify that the Score Candidate node returns valid JSON with a numeric score before connecting the rest of the workflow.

  • Click Test workflow in the top menu bar to run the full chain from the Gmail Trigger through to the Score Candidate node.
  • If you no longer have the test email in your inbox, send a new email with a PDF resume attachment to your Gmail before testing.
  • Click on the Score Candidate node after the run completes to inspect its output.

You should see a JSON object in the output panel containing a score field with a number between 0 and 100, a match_reasons string, a skill_matches array, and a missing_skills array.

Seeing an error or malformed output?

  • If the output is not valid JSON, check that the Structured Output Parser schema matches the example exactly. The field names and types must align.
  • If you see a connection error to Ollama, verify your Ollama credentials URL in n8n. Use http://host.docker.internal:11434 on Docker Desktop (macOS/Windows) or the Ollama container name if both are on the same Docker network.
  • If the score field returns as a string instead of a number, re-run the test. Small models occasionally format inconsistently on the first attempt.

Still stuck? Ask for help.

Your workflow now parses resumes and scores candidates automatically. Next up, you'll upload the PDFs to Google Drive and log everything to your Google Sheets dashboard so you can see all candidates ranked by score.

Upload Resume to Drive and Log to Google Sheets

Your scoring node now produces a numeric score and detailed match reasons for every candidate.

The final piece is getting that data somewhere useful. In this step, you'll store the original resume PDF in Google Drive for reference and log the parsed candidate data to your Google Sheets dashboard. Then you'll activate the workflow so it runs automatically.

In this step, get ready to:
  • Upload resume PDFs to your Google Drive "Resumes" folder.
  • Append scored candidate data to your Google Sheets dashboard.
  • Activate the workflow so it screens resumes automatically.
Upload the resume PDF to Google Drive

Before logging data to the spreadsheet, you want to keep a copy of the original resume PDF. The Google Drive node uploads the binary attachment directly from the Gmail Get node's output into your "Resumes" folder.

  • In your n8n workflow canvas, click the + button after the Score Candidate node to add a new node.
  • Search for Google Drive and select it.
  • Set the Operation to Upload.
  • In the Folder field, select your Resumes folder from Google Drive.
  • In the Input Binary Field field, click the expression toggle (the small { } icon) and paste this expression:
{{ $('Gmail Get').item.binary.attachment_0 }}

What does this expression do?

This expression reaches back to the Gmail Get node (not the current node) and grabs the binary PDF attachment stored in attachment_0. Without this cross-node reference, the Google Drive node wouldn't have access to the original PDF data because it only receives the scoring output by default.

Why attachment_0?

n8n names downloaded email attachments sequentially: attachment_0 for the first file, attachment_1 for the second, and so on. If your test email has only one PDF, attachment_0 is always correct.

You can verify the exact name by opening the Gmail Get node's output panel and checking the binary data tab. The field name is shown there.

  • In the File Name field, click the expression toggle and paste this expression to name files with the candidate's name and date:
{{ $('Parse Resume').item.json.output.name }}_{{ $now.toFormat('yyyy-MM-dd') }}.pdf
  • Click Test step to run the Google Drive node with your existing test data.
  • Open your Resumes folder in Google Drive. You should see the uploaded PDF with a filename containing the candidate's name and today's date.

File not appearing in Google Drive?

Make sure you selected the correct Google Drive credentials (the same ones connected in n8n for your Google account).

Check that the Folder field points to your "Resumes" folder specifically. If you type it manually, the name must match exactly.

If you see an error about binary data, confirm that the Gmail Get node ran successfully earlier in the test. The Google Drive node depends on that output existing.

Help me troubleshoot my Google Drive upload node in n8n

Log candidate data to Google Sheets

Now that the PDF is stored, you need to log the structured candidate data to your dashboard. The Google Sheets node appends a new row for each candidate with their score, skills, and contact details.

  • Click the + button after the Google Drive node to add a new node.
  • Search for Google Sheets and select it.
  • Set the Operation to Append Row.
  • In the Document field, select your Resume Screening Dashboard spreadsheet.
  • Select Sheet1 (or whatever your sheet tab is named) in the Sheet field.

n8n detects your column headers automatically. You need to map each column to the correct expression. For each column below, click the expression toggle on the field and paste the corresponding expression.

  • In the Score field, paste:
{{ $json.output.score }}
  • In the Name field, paste:
{{ $('Parse Resume').item.json.output.name }}
  • In the Email field, paste:
{{ $('Parse Resume').item.json.output.email }}
  • In the Key Skills field, paste:
{{ $json.output.skill_matches.join(', ') }}
  • In the Experience Years field, paste:
{{ $('Parse Resume').item.json.output.experience_years }}
  • In the Match Reasons field, paste:
{{ $json.output.match_reasons }}
  • In the Date Received field, paste:
{{ $now.toFormat('yyyy-MM-dd HH:mm') }}
  • In the Subject Line field, paste:
{{ $('Gmail Trigger').item.json.subject }}

What do these expressions reference?

Some expressions start with $json which refers to the output of the immediately previous node (Score Candidate). Others use $('Node Name') to reach back to a specific earlier node in the chain.

The .join(', ') on Key Skills converts the array of matched skills into a comma-separated string for clean spreadsheet display.

  • Click Test step to run the Google Sheets node.
  • Open your Resume Screening Dashboard spreadsheet in Google Sheets. You should see a new row with the candidate's score, name, email, skills, experience, match reasons, date, and subject line filled in.

Row not appearing in Google Sheets?

Check that the column headers in your spreadsheet match exactly what n8n detected. If you renamed columns after connecting the node, click Refresh in the columns mapping to re-detect them.

If a column value shows as empty, the expression might be referencing a node name that doesn't match your workflow. Double-check that your Parse Resume node is named exactly Parse Resume (with that capitalization and spacing).

Help me fix my Google Sheets column mapping in n8n

Activate the workflow

Testing manually confirms the workflow works end-to-end. Activating it tells n8n to poll Gmail automatically every 5 minutes and process new resumes without any manual intervention.

  • Click the Active toggle in the top-right corner of the workflow editor. It turns green when enabled.

Your automation is now live. Every 5 minutes, n8n checks your Gmail for new emails with PDF attachments, extracts the resume text, scores the candidate, uploads the PDF to Drive, and logs everything to your dashboard.

Test with a real email

Send yourself an email with a PDF resume attached from a different email address. Wait 5 minutes (or up to 10 to be safe), then check your Google Sheet for a new row. This confirms the full automated loop is working without any manual triggers.

Workflow not triggering on new emails?

Make sure the toggle is green (active). If it turns off after a few seconds, check the execution log for errors by clicking Executions in the left sidebar.

The Gmail Trigger polls every 5 minutes. If you just sent the email, wait the full interval before checking. Also confirm the test email actually has a PDF attachment, since the filter has:attachment filename:pdf only matches PDF files.

Help me debug why my n8n workflow isn't triggering automatically

✔️ Awesome, I've got everything!

Great. Double-check that your workflow has 7 nodes connected in sequence and the toggle is active.

ⓧ I'd like to double check the full workflow

Your complete workflow should have these 7 nodes connected in this order:

  • Gmail Trigger - Simplify OFF, filter: has:attachment filename:pdf, poll every 5 minutes.
  • Gmail Get - Operation: Get, Message ID: {{ $json.id }}, Download Attachments enabled.
  • Extract From File - Operation: Extract From PDF, Input Binary Field: attachment_0.
  • Parse Resume (Basic LLM Chain) - Ollama qwen2.5:3b, temperature 0.1, Structured Output Parser with name/email/skills/experience_years/education/summary schema.
  • Score Candidate (Basic LLM Chain) - Ollama qwen2.5:3b, temperature 0.1, Structured Output Parser with score/match_reasons/skill_matches/missing_skills schema.
  • Google Drive Upload - Folder: Resumes, Binary: {{ $('Gmail Get').item.binary.attachment_0 }}, filename includes candidate name and date.
  • Google Sheets Append Row - Spreadsheet: Resume Screening Dashboard, all 8 columns mapped (Score, Name, Email, Key Skills, Experience Years, Match Reasons, Date Received, Subject Line).

The Active toggle in the top-right corner should be green.

Secret mission

Add a High-Score Notification

Your dashboard logs every candidate, but what about the ones who stand out? Add a notification step so you get alerted the moment a candidate scores 80 or above. Never miss top talent again.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine using Docker and free Google services. There are no ongoing cloud costs.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • n8n workflow (self-hosted Docker container).
  • Ollama with the qwen2.5:3b model.
  • Google Sheet: "Resume Screening Dashboard".
  • Google Drive folder: "Resumes".
  • Gmail Trigger polling your inbox every 5 minutes.

Keep everything running

No action needed. Choose this if you want the automation to keep screening incoming resumes in real time.

  • Your n8n Docker container stays running and the workflow remains active.
  • Ollama continues serving the qwen2.5:3b model locally.
  • New resumes sent to your Gmail will be processed and logged to Google Sheets automatically.

Pause - I'll come back to this later

Shut down running processes to free up memory and CPU. Your workflow and data stay intact for when you return.

  • In n8n, click the active toggle in the top-right corner of your workflow to deactivate it. This stops the Gmail polling.
  • Stop your n8n Docker container by running docker stop n8n in your terminal.
  • Stop Ollama by running ollama stop qwen2.5:3b to unload the model from memory.
  • Your Google Sheet and Drive folder remain untouched. When you are ready to resume, start Docker and reactivate the workflow.

Delete - I don't want to use this again

Remove all project resources and start fresh. This is permanent.

  • In n8n, open your resume screening workflow, click the three-dot menu, and select Delete.
  • Remove the Ollama model by running ollama rm qwen2.5:3b in your terminal. This frees approximately 2GB of disk space.
  • Delete the "Resume Screening Dashboard" Google Sheet from your Google Drive.
  • Delete the "Resumes" folder in Google Drive.
  • If you no longer need n8n, remove its Docker container and image by running docker rm n8n followed by docker rmi n8nio/n8n.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully automated, self-hosted resume screening pipeline that monitors your inbox, evaluates candidates with AI, and ranks them in a live dashboard.

Here's what you accomplished:

  • Built a 7-node n8n workflow that polls Gmail for resume emails, downloads PDF attachments, and processes them end-to-end without manual intervention.
  • Used a local LLM (qwen2.5:3b via Ollama) to parse unstructured PDF text into structured candidate data and score each resume against a custom 6-criteria rubric.
  • Deployed a live Google Sheets dashboard that automatically logs and ranks candidates by score, with skills, experience, and match reasons visible at a glance.
  • Added smart notifications for high-scoring candidates so you never miss top talent.

Ready to quiz yourself?