AutomationFlowsAI & RAG › Screen and Log Gmail Resumes with Google Gemini and Supabase

Screen and Log Gmail Resumes with Google Gemini and Supabase

ByKanishka Shrivastava @kanishka-shrivastava on n8n.io

This workflow monitors Gmail for incoming job applications with resume attachments, extracts text from the PDF, and uses Google Gemini to screen candidates against your role requirements. It logs every result to Supabase, then either emails the recruiter, creates a rejection…

Event trigger★★★★☆ complexityAI-powered18 nodesGmail TriggerChain LlmGoogle Gemini ChatOutput Parser StructuredSupabaseGmail
AI & RAG Trigger: Event Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #17281 — we link there as the canonical source.

This workflow follows the Chainllm → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "8qGkDG1Wvuk4Ds6K",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Screen resumes from Gmail with Gemini",
  "tags": [],
  "nodes": [
    {
      "id": "af8d5c1c-ef28-45da-820a-a77bebdbf0a5",
      "name": "Template Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 680,
        "height": 1064,
        "content": "## \ud83d\udcc4 Screen Resumes from Gmail with Gemini\n\n## Who's it for\nSmall businesses and recruiters who get job applications by email and want resumes automatically screened against the role's requirements, without spending time reading every single one, while keeping a human in the loop before anyone actually gets rejected.\n\n## How it works\nWhen an email with an attachment arrives, the workflow extracts the resume text and asks Gemini to screen it against your job title, required skills, and minimum experience. Every candidate is logged regardless of outcome. Shortlisted candidates trigger an email straight to the recruiter. Clear rejections get a polite reply saved as a Gmail draft, not sent automatically, so a human still approves it before it goes out. Anything ambiguous is labeled for manual review instead of being auto-decided either way.\n\n## How to set up\n1. Connect your Gmail OAuth2 credential to the trigger and both Gmail action nodes.\n2. Connect your Google Gemini API credential to the Chat Model node.\n3. Connect your Supabase credentials and create the table described in the sticky note near the Log Candidate node.\n4. In Gmail, create a label (e.g. \"Needs Manual Review\") and find its label ID.\n5. Open the Set Configuration node and fill in the job title, required skills, minimum experience, recruiter's email, and the label ID.\n6. Activate the workflow.\n\n## Requirements\n- A Gmail account with OAuth2 credentials in n8n\n- A Google Gemini API key\n- A Supabase project\n\n## How to customize\nEdit the screening prompt to change how strict the shortlist/reject line is, or to weigh certain skills more heavily. Edit the rejection draft's wording to match your company's tone."
      },
      "typeVersion": 1
    },
    {
      "id": "e3d3691a-8252-410f-ac2b-a34e608374fe",
      "name": "Section - Trigger and Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 340,
        "content": "## 1\ufe0f\u20e3 2\ufe0f\u20e3 Trigger + Configuration\nFires on emails with attachments. Verify the filter query matches how applications actually arrive in your inbox (e.g. add a subject filter if you use a dedicated \"careers@\" address)."
      },
      "typeVersion": 1
    },
    {
      "id": "5b9953bb-16dd-4d8b-b26d-4c8f35a35199",
      "name": "Section - Extract and Screen",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 872,
        "height": 608,
        "content": "## 3\ufe0f\u20e3 4\ufe0f\u20e3 Extract + Screen\nPulls text out of the attached resume, then Gemini compares it against your job requirements. \u26a0\ufe0f Verify the binary field name below matches what your Gmail attachments actually come through as (commonly attachment_0)."
      },
      "typeVersion": 1
    },
    {
      "id": "cfa8fabe-f530-4413-911f-6e0bc370b47b",
      "name": "Supabase Schema Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1584,
        736
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "content": "\u26a0\ufe0f Table schema\ncandidates: candidate_name, years_experience, matched_skills (jsonb), missing_skills (jsonb), decision, reason, created_at (timestamptz, default now())."
      },
      "typeVersion": 1
    },
    {
      "id": "55ac9e06-3d8e-49d4-a218-6a2ed5337533",
      "name": "Section - Shortlist Branch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 324,
        "content": "## 5\ufe0f\u20e3 If Shortlisted\nSends the recruiter a real email right away, this is internal and low-risk, so no draft-only caution needed here."
      },
      "typeVersion": 1
    },
    {
      "id": "d6f521d2-a2a4-413c-81e5-0228afba391f",
      "name": "Section - Reject Branch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 444,
        "height": 412,
        "content": "## 6\ufe0f\u20e3 If Rejected\nSaves a polite rejection as a Gmail draft, never sent automatically. A human still reviews and hits send, same draft-only pattern used elsewhere: the AI shouldn't be the one deciding a real candidate gets turned away without a second look."
      },
      "typeVersion": 1
    },
    {
      "id": "b7625ded-ebbb-4702-a7c1-2d5a7f937026",
      "name": "Section - Review Branch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 448,
        "content": "## 7\ufe0f\u20e3 If Ambiguous\nNo automatic decision either way, just labeled so a person makes the final call."
      },
      "typeVersion": 1
    },
    {
      "id": "7ca9195b-2feb-4cc9-8b6c-ca1a557fd877",
      "name": "New Application Email",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        464,
        544
      ],
      "parameters": {
        "filters": {
          "q": "has:attachment"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "135e1c4f-e553-4de7-8ec8-8e96118d3fad",
      "name": "Set Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        768,
        544
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-1",
              "name": "job_title",
              "type": "string",
              "value": "REPLACE_WITH_JOB_TITLE"
            },
            {
              "id": "cfg-2",
              "name": "required_skills",
              "type": "string",
              "value": "REPLACE_WITH_COMMA_SEPARATED_SKILLS"
            },
            {
              "id": "cfg-3",
              "name": "min_experience_years",
              "type": "number",
              "value": 2
            },
            {
              "id": "cfg-4",
              "name": "recruiter_email",
              "type": "string",
              "value": "REPLACE_WITH_RECRUITER_EMAIL"
            },
            {
              "id": "cfg-5",
              "name": "supabase_table_candidates",
              "type": "string",
              "value": "candidates"
            },
            {
              "id": "cfg-6",
              "name": "needs_review_label_id",
              "type": "string",
              "value": "REPLACE_WITH_GMAIL_LABEL_ID"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f249701c-a1e0-4720-a287-a1f4cb42b271",
      "name": "Extract Resume Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1088,
        544
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "attachment_0"
      },
      "typeVersion": 1
    },
    {
      "id": "a16023d6-6c29-480e-9299-971a191d5a55",
      "name": "Screen Candidate",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1328,
        544
      ],
      "parameters": {
        "text": "=You are screening a resume for this role.\n\nJob title: {{ $('Set Configuration').item.json.job_title }}\nRequired skills: {{ $('Set Configuration').item.json.required_skills }}\nMinimum experience: {{ $('Set Configuration').item.json.min_experience_years }} years\n\nResume text:\n{{ $json.text }}\n\nExtract the candidate's name and years of experience, list which required skills they clearly match and which are missing, and decide: \"shortlist\" if they clearly meet the requirements, \"reject\" if they clearly don't, or \"needs_human_review\" if it's genuinely unclear or borderline. Give a short reason for your decision.",
        "batching": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "63c4cd8c-ff69-4f01-9039-04949f443d20",
      "name": "Screening Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1248,
        768
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "22be2a37-0d96-43b4-86a2-b71537181b0a",
      "name": "Screening Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1424,
        784
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"candidate_name\": {\n      \"type\": \"string\"\n    },\n    \"years_experience\": {\n      \"type\": \"number\"\n    },\n    \"matched_skills\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"missing_skills\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"decision\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"shortlist\",\n        \"reject\",\n        \"needs_human_review\"\n      ]\n    },\n    \"reason\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\n    \"candidate_name\",\n    \"years_experience\",\n    \"matched_skills\",\n    \"missing_skills\",\n    \"decision\",\n    \"reason\"\n  ]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "d33e4a98-8a13-44d5-be0a-3d74705cce99",
      "name": "Log Candidate",
      "type": "n8n-nodes-base.supabase",
      "position": [
        1680,
        544
      ],
      "parameters": {
        "operation": "insert"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a7df3d96-5846-459f-bd9e-3e8ddc532687",
      "name": "Route by Decision",
      "type": "n8n-nodes-base.switch",
      "position": [
        1952,
        512
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "b5fb4285-c8d7-4f5d-b963-7565195350de",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Screen Candidate').item.json.output.decision }}",
                    "rightValue": "shortlist"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "fce356fb-bd48-4042-a14b-255c08e5755b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Screen Candidate').item.json.output.decision }}",
                    "rightValue": "reject"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "96d9db43-0d05-4cfb-81d9-5b381d2aafb4",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Screen Candidate').item.json.output.decision }}",
                    "rightValue": "needs_human_review"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "3c8ff4dd-3487-418e-b153-57d25440b8bc",
      "name": "Notify Recruiter",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2368,
        208
      ],
      "parameters": {
        "sendTo": "={{ $('Set Configuration').item.json.recruiter_email }}",
        "message": "=Candidate: {{ $('Screen Candidate').item.json.output.candidate_name }}\nExperience: {{ $('Screen Candidate').item.json.output.years_experience }} years\nMatched skills: {{ $('Screen Candidate').item.json.output.matched_skills.join(', ') }}\nReason: {{ $('Screen Candidate').item.json.output.reason }}",
        "options": {},
        "subject": "=Shortlisted candidate: {{ $('Screen Candidate').item.json.output.candidate_name }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "22eca8d8-58ac-40e6-a4f2-e733ce0329a2",
      "name": "Save Rejection as Gmail Draft",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2400,
        608
      ],
      "parameters": {
        "message": "=Hi {{ $('Screen Candidate').item.json.output.candidate_name }},\n\nThank you for applying for the {{ $('Set Configuration').item.json.job_title }} role. After reviewing your application, we've decided to move forward with other candidates whose experience more closely matches what we're looking for right now.\n\nWe appreciate the time you took to apply and wish you the best in your search.\n\nBest regards",
        "options": {
          "threadId": "={{ $('New Application Email').item.json.threadId }}"
        },
        "subject": "=Re: Your application for {{ $('Set Configuration').item.json.job_title }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d7482a1c-2e41-4de9-9586-2d026631ae23",
      "name": "Label Email as Needs Review",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2352,
        1040
      ],
      "parameters": {
        "labelIds": [
          "={{ $('Set Configuration').item.json.needs_review_label_id }}"
        ],
        "messageId": "={{ $('New Application Email').item.json.id }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "90531c62-4f39-4fab-a167-4381d87e2588",
  "nodeGroups": [],
  "connections": {
    "Log Candidate": {
      "main": [
        [
          {
            "node": "Route by Decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Screen Candidate": {
      "main": [
        [
          {
            "node": "Log Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Screening Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Screen Candidate",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Route by Decision": {
      "main": [
        [
          {
            "node": "Notify Recruiter",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save Rejection as Gmail Draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Label Email as Needs Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Configuration": {
      "main": [
        [
          {
            "node": "Extract Resume Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Resume Text": {
      "main": [
        [
          {
            "node": "Screen Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Screening Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Screen Candidate",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "New Application Email": {
      "main": [
        [
          {
            "node": "Set Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow monitors Gmail for incoming job applications with resume attachments, extracts text from the PDF, and uses Google Gemini to screen candidates against your role requirements. It logs every result to Supabase, then either emails the recruiter, creates a rejection…

Source: https://n8n.io/workflows/17281/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow creates a sophisticated, self-improving customer support system that automatically handles incoming emails. It's designed to answer common questions using an AI-powered knowledge base an

Text Classifier, Chain Llm, Gmail Trigger +4
AI & RAG

Perfect for use cases such as: 💼 Finance teams managing vendor invoices 📊 Bookkeeping workflows 🔄 Automating monthly reconciliation

Output Parser Structured, HTTP Request, Gmail Trigger +4
AI & RAG

This workflow monitors Gmail for unread invoice emails with PDF attachments, extracts invoice details with Google Gemini, verifies Japanese qualified invoice registration numbers against the National

Gmail Trigger, Chain Llm, Google Gemini Chat +5
AI & RAG

This workflow monitors Gmail for unread emails with PDF attachments, uses Google Gemini to extract Japan Electronic Books Preservation Act filing metadata, logs a searchable index in Google Sheets, an

Gmail Trigger, Chain Llm, Google Gemini Chat +5
AI & RAG

This workflow polls Gmail for unread travel confirmation emails, uses Google Gemini to detect real bookings and extract structured trip details, then upserts each booking into a Google Sheets itinerar

Gmail Trigger, Chain Llm, Google Gemini Chat +3