{
  "id": "WsgaLWQbatUHVqmC",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Screen, score and route job applicants with Gemini, Gmail, Sheets and Slack",
  "tags": [],
  "nodes": [
    {
      "id": "2d5edc1a-4e62-4e04-9276-49cc24038160",
      "name": "Schedule Daily Run",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        48,
        48
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "4790bddd-069f-4168-a1c0-10fa7acb0538",
      "name": "Get New Applicants",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        48
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "Applicants"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "Select your spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "24623ae2-028b-4a4c-a1ba-137a7b7b5d15",
      "name": "Set Job Requirements",
      "type": "n8n-nodes-base.set",
      "position": [
        480,
        48
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "role",
              "name": "jobTitle",
              "type": "string",
              "value": "Senior Backend Engineer"
            },
            {
              "id": "must",
              "name": "mustHave",
              "type": "string",
              "value": "Python, cloud (AWS/GCP), 5+ years backend"
            },
            {
              "id": "nice",
              "name": "niceToHave",
              "type": "string",
              "value": "Team leadership, Kubernetes"
            },
            {
              "id": "req",
              "name": "requirements",
              "type": "string",
              "value": "Owns backend services end to end."
            },
            {
              "id": "min",
              "name": "minScore",
              "type": "number",
              "value": 70
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "fbf7e37f-bc25-409e-ab3e-5835304df83c",
      "name": "Build Screening Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        48
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const cfg = $('Set Job Requirements').item.json;\nconst a = $json;\nconst name = a.Name||a.name||'';\nconst email = a.Email||a.email||'';\nconst resume = a.Resume||a.resume||a.ResumeText||a.experience||'';\nconst min = cfg.minScore||70;\nconst prompt = [\n  'You are a technical recruiter. Score the candidate against the role and respond ONLY with minified JSON:',\n  '{\"score\":number,\"verdict\":\"strong\"|\"possible\"|\"weak\",\"strengths\":string[],\"gaps\":string[],\"red_flags\":string[],\"summary\":string}',\n  'verdict: strong if score>=' + min + ', possible if 50-' + (min-1) + ', otherwise weak.',\n  '',\n  'Role: ' + (cfg.jobTitle||''),\n  'Must-have: ' + (cfg.mustHave||''),\n  'Nice-to-have: ' + (cfg.niceToHave||''),\n  'Requirements: ' + (cfg.requirements||''),\n  '',\n  'Example -> {\"score\":82,\"verdict\":\"strong\",\"strengths\":[\"Python\",\"AWS\"],\"gaps\":[\"No GCP\"],\"red_flags\":[],\"summary\":\"Strong backend match\"}',\n  '',\n  'Candidate: ' + name,\n  'Resume:',\n  resume\n].join('\\n');\nreturn { prompt, name, email, role: cfg.jobTitle };"
      },
      "typeVersion": 2
    },
    {
      "id": "fbe86081-847d-49ed-96bd-4fea8f9d6b7d",
      "name": "Score Applicant with Gemini",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        976,
        48
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { \"contents\": [ { \"parts\": [ { \"text\": $json.prompt } ] } ], \"generationConfig\": { \"temperature\": 0.1, \"responseMimeType\": \"application/json\" } } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googlePalmApi"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "688e036f-acc2-4f6b-bdc7-8319c2007c58",
      "name": "Parse Score",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        32
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const meta = $('Build Screening Prompt').item.json;\nlet text = '';\ntry { text = $json.candidates[0].content.parts[0].text; } catch (e) { text = ''; }\nlet d = {};\ntry { d = JSON.parse(text); } catch (e) { d = { score:0, verdict:'weak', strengths:[], gaps:[], red_flags:[], summary:(text||'').slice(0,200) }; }\nconst arr = (x) => Array.isArray(x) ? x.join('; ') : (x||'');\nreturn { name: meta.name, email: meta.email, role: meta.role, score: Number(d.score)||0, verdict: String(d.verdict||'weak').toLowerCase(), strengths: arr(d.strengths), gaps: arr(d.gaps), redFlags: arr(d.red_flags), summary: d.summary||'' };"
      },
      "typeVersion": 2
    },
    {
      "id": "0790c265-ac64-4dc7-a9fa-13f4094b337e",
      "name": "Update Applicant Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1616,
        32
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "Applicants"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "Select your spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c9563c0f-1f93-4400-9316-1dcc3b8dec7e",
      "name": "Route by Verdict",
      "type": "n8n-nodes-base.switch",
      "position": [
        2016,
        0
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $(\"Parse Score\").item.json.verdict }}",
                    "rightValue": "strong"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $(\"Parse Score\").item.json.verdict }}",
                    "rightValue": "possible"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $(\"Parse Score\").item.json.verdict }}",
                    "rightValue": "weak"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Other"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "6ff1f43d-12c2-46aa-9e8a-d6a11f4b217f",
      "name": "Notify Hiring Team",
      "type": "n8n-nodes-base.slack",
      "position": [
        2240,
        -16
      ],
      "parameters": {
        "text": "=*:star: Strong candidate for {{ $(\"Parse Score\").item.json.role }}*\n*Name:* {{ $(\"Parse Score\").item.json.name }}\n*Score:* {{ $(\"Parse Score\").item.json.score }}\n*Strengths:* {{ $(\"Parse Score\").item.json.strengths }}\n*Summary:* {{ $(\"Parse Score\").item.json.summary }}\nAn interview-invite draft is ready in Gmail.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "#hiring"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "35829d1e-f814-4627-abca-0f6ef37a33fb",
      "name": "Draft Interview Invite",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2464,
        -16
      ],
      "parameters": {
        "message": "={{ \"Hi \" + $(\"Parse Score\").item.json.name + \",\\n\\nThanks for applying for the \" + $(\"Parse Score\").item.json.role + \" role. We were impressed by your background and would love to invite you to an interview. Please let us know your availability this week.\\n\\nBest regards\" }}",
        "options": {
          "sendTo": "={{ $(\"Parse Score\").item.json.email }}"
        },
        "subject": "={{ \"Interview invitation - \" + $(\"Parse Score\").item.json.role }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "36cb1e27-cff5-4eed-b37f-6dff3d6e04ca",
      "name": "Flag for Human Review",
      "type": "n8n-nodes-base.slack",
      "position": [
        2240,
        144
      ],
      "parameters": {
        "text": "=*:eyes: Borderline candidate - needs review*\n*Name:* {{ $(\"Parse Score\").item.json.name }}\n*Score:* {{ $(\"Parse Score\").item.json.score }}\n*Gaps:* {{ $(\"Parse Score\").item.json.gaps }}\n*Summary:* {{ $(\"Parse Score\").item.json.summary }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "#hiring-review"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "67e2a576-a22f-47c8-85d7-7c032304815b",
      "name": "Draft Polite Rejection",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2240,
        304
      ],
      "parameters": {
        "message": "={{ \"Hi \" + $(\"Parse Score\").item.json.name + \",\\n\\nThank you for your interest in the \" + $(\"Parse Score\").item.json.role + \" role. After careful review we have decided not to move forward at this time. We truly appreciate your interest and wish you the best in your search.\\n\\nBest regards\" }}",
        "options": {
          "sendTo": "={{ $(\"Parse Score\").item.json.email }}"
        },
        "subject": "={{ \"Your application for \" + $(\"Parse Score\").item.json.role }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6986e58d-af71-406f-b345-1d3039a45653",
      "name": "Flag Unscored for Review",
      "type": "n8n-nodes-base.slack",
      "position": [
        2240,
        464
      ],
      "parameters": {
        "text": "=*:mag: Applicant could not be classified*\n*Name:* {{ $(\"Parse Score\").item.json.name }}\nPlease review manually.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "#hiring-review"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "b4fb1461-cf97-4324-bfb5-f838859d775a",
      "name": "Report Scoring Failure",
      "type": "n8n-nodes-base.slack",
      "position": [
        1280,
        208
      ],
      "parameters": {
        "text": "=*:x: Gemini scoring failed for {{ $(\"Build Screening Prompt\").item.json.name }}*\nPlease review this applicant manually.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "#hiring"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "6c4fd580-b717-4385-b226-f76855cb1d84",
      "name": "Overview Sticky",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        -240
      ],
      "parameters": {
        "width": 440,
        "height": 820,
        "content": "## Screen, score and route job applicants with Gemini, Gmail, Sheets and Slack\n\n### How it works\nEach day the workflow reads only **new** applicants (rows without a Status) and asks Google Gemini to score them against your role, returning a score, verdict, strengths, gaps and red flags. The applicant row is updated with the result and marked Screened, so nobody is scored twice. A Switch then routes by verdict: strong candidates trigger a Slack alert plus an interview-invite draft, borderline ones are flagged for human review, weak ones get a polite rejection draft, and unscored ones are flagged. Scoring failures are reported to Slack.\n\n### Setup\n1. Connect Google Sheets, Google Gemini (PaLM) API, Gmail and Slack.\n2. Add applicants with columns Name, Email, Resume and an empty Status column.\n3. In **Set Job Requirements**, set the role, must-haves and minimum score.\n\n### Customization tips\nAdjust the score bands, add skills to weigh, or auto-send the drafts."
      },
      "typeVersion": 1
    },
    {
      "id": "7cff5dc7-db84-456a-b78a-2e4a3f35b5d3",
      "name": "S1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 900,
        "content": "## 1. Fetch new applicants & configure\nRead only rows without a Status (new applicants) and set the role requirements."
      },
      "typeVersion": 1
    },
    {
      "id": "d99b188c-26e9-48f0-a02e-f91896595364",
      "name": "S2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 900,
        "content": "## 2. AI scoring + error handling\nGemini scores each candidate as JSON. If the call fails, Slack is notified instead of dropping the applicant."
      },
      "typeVersion": 1
    },
    {
      "id": "aa056e25-8e87-49ff-9b1a-301c9e5f7d9b",
      "name": "S3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1504,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 364,
        "height": 900,
        "content": "## 3. Persist & de-duplicate\nUpdate the applicant row (match on Email) and set Status = Screened so they are never scored twice."
      },
      "typeVersion": 1
    },
    {
      "id": "b6f66596-a800-4e65-aeea-2fa760f58ce0",
      "name": "S4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1888,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 900,
        "content": "## 4. Route by verdict & act\nStrong -> Slack alert + interview-invite draft. Possible -> human review. Weak -> rejection draft. Unscored -> manual review."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "0c1cabc3-5437-4d25-8213-4747e432fe41",
  "nodeGroups": [],
  "connections": {
    "Parse Score": {
      "main": [
        [
          {
            "node": "Update Applicant Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Verdict": {
      "main": [
        [
          {
            "node": "Notify Hiring Team",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag for Human Review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Draft Polite Rejection",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag Unscored for Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get New Applicants": {
      "main": [
        [
          {
            "node": "Set Job Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Hiring Team": {
      "main": [
        [
          {
            "node": "Draft Interview Invite",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Daily Run": {
      "main": [
        [
          {
            "node": "Get New Applicants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Job Requirements": {
      "main": [
        [
          {
            "node": "Build Screening Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Applicant Row": {
      "main": [
        [
          {
            "node": "Route by Verdict",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Screening Prompt": {
      "main": [
        [
          {
            "node": "Score Applicant with Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Applicant with Gemini": {
      "main": [
        [
          {
            "node": "Parse Score",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Report Scoring Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}