{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "433bbc96-565d-47da-811f-4936022a9861",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        1216
      ],
      "parameters": {
        "color": 5,
        "width": 1400,
        "height": 280,
        "content": "## \ud83e\udd16 AI Resume Screener & Automated HR Notifier\n\nAutomatically scores job applicants using Google Gemini and routes personalised emails to HR and candidates.\n\n**How it works:** Polls a Google Sheet every minute \u2192 skips already-processed rows \u2192 maps the applied role to a Job Description \u2192 asks Gemini to score the candidate 1\u201310 \u2192 writes the analysis back to the sheet \u2192 sends an HR alert and candidate email based on the score.\n\n**Setup checklist:**\n- Google Sheets OAuth2 credential \u2192 connect to Trigger and HTTP Request nodes\n- Google Gemini API key \u2192 connect to the Gemini Chat Model node\n- Replace Sheet ID `1iv-8fToAzxBm8ht5vg-UP_p_hQzK4-....` in all Google Sheet nodes\n- Update `fromEmail` / `toEmail` in the three Send Email nodes\n- Extend `JD_MAP` in the Code node with your own open roles"
      },
      "typeVersion": 1
    },
    {
      "id": "e6176f2d-8b14-4512-9189-d28c6049a258",
      "name": "Step 1 \u2014 Trigger & Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        1504
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 524,
        "content": "### \ud83d\udce5 Step 1 \u2014 Trigger & Filter\n\n**Google Sheets Trigger** polls every minute and fires when a new row is added.\n\n**Read All Rows** re-fetches the full sheet so the filter has the latest state of every row.\n\n**Filter Unprocessed Rows** skips any row where column R (`col_18`) is already `'1'`, preventing duplicate processing."
      },
      "typeVersion": 1
    },
    {
      "id": "027c71cd-96ce-4291-a11c-971d4023d1f5",
      "name": "Step 2 \u2014 AI Assessment",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        1504
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 528,
        "content": "### \ud83e\udde0 Step 2 \u2014 AI Assessment\n\n**Extract Fields & Load JD** pulls the candidate's name, email, position, experience, and skills. It then looks up the matching Job Description from the built-in `JD_MAP`.\n\n**AI Resume Scorer** sends a structured prompt to Gemini requesting a raw JSON response containing: `score` (1\u201310), `grade`, `strengths`, `weaknesses`, `recommendation`, and `summary`.\n\n**Google Gemini Chat Model** is the sub-node that powers the AI agent above.\n\n**Parse AI Output** safely parses Gemini's JSON string and adds a `status` field: `'Shortlisted'` if score \u2265 7, otherwise `'Rejected'`."
      },
      "typeVersion": 1
    },
    {
      "id": "158a24a4-956f-4120-a7e1-29fda0123fb6",
      "name": "Step 3 \u2014 Sheet Update",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        1520
      ],
      "parameters": {
        "color": 3,
        "width": 420,
        "height": 508,
        "content": "### \ud83d\udcca Step 3 \u2014 Sheet Update\n\n**Build Sheet Request** constructs the Sheets API PUT URL and body, targeting columns J\u2013R on the exact row number of the applicant.\n\n**Update Sheet Row** calls the Google Sheets API directly via HTTP Request (using OAuth2) to write all 9 fields in a single operation \u2014 including setting column R to `'1'` as the processed flag."
      },
      "typeVersion": 1
    },
    {
      "id": "4701a168-c9fd-40c1-a366-2cf87dbdf604",
      "name": "Step 4 \u2014 Routing & Emails",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        1392
      ],
      "parameters": {
        "color": 3,
        "width": 720,
        "height": 736,
        "content": "### \ud83d\udce7 Step 4 \u2014 Score Routing & Emails\n\n**Check Score \u2265 7** branches the flow:\n\n**\u2705 True (shortlist path)**\n- **Alert HR Team** \u2014 notifies the HR inbox with the candidate's score and strengths.\n- **Wait 5s** \u2014 small delay before the candidate email goes out.\n- **Notify Shortlisted Candidate** \u2014 sends the applicant a congratulations and interview invitation.\n\n**\u274c False (rejection path)**\n- **Notify Rejected Candidate** \u2014 sends a polite, professional decline email."
      },
      "typeVersion": 1
    },
    {
      "id": "2e51960c-5ff0-4211-979a-5f19ac271124",
      "name": "Filter Unprocessed Rows",
      "type": "n8n-nodes-base.code",
      "position": [
        800,
        1760
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "\nconst row = $input.item.json;\n\n// Normalize keys\nconst r = {};\nfor (const key of Object.keys(row)) {\n  r[key.trim()] = (row[key] ?? '').toString().trim();\n}\n\nconst email = r['Email Address'] || '';\nif (!email) return null; // skip rows with no email\n\n// col_18 = column R = Processed\nconst processedByCol = (row['col_18'] ?? '').toString().trim();\nconst processedByName = r['Processed'] || '';\nconst processed = processedByCol || processedByName;\n\nif (processed === '1') {\n  return null; // skip\n}\n\nreturn { json: row };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3eaf0b93-cb68-4513-b495-e78fb4e1685d",
      "name": "Wait 5s",
      "type": "n8n-nodes-base.wait",
      "position": [
        2368,
        1760
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "4f3b8291-cc37-4ec0-893c-0792c0462c25",
      "name": "Google Sheets Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        352,
        1760
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 525556004
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1iv-8fToAzxBm8ht5vg-UP_p_hQzK4-uqs2KWu06im4o"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "091927be-bdd9-426a-97f6-16029726c5fd",
      "name": "Read All Rows",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        576,
        1760
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 525556004
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1iv-8fToAzxBm8ht5vg-UP_p_hQzK4-uqs2KWu06im4o"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "255e4cc8-2c04-4e80-aadc-99337b04420a",
      "name": "Extract Fields & Load JD",
      "type": "n8n-nodes-base.code",
      "position": [
        1024,
        1760
      ],
      "parameters": {
        "jsCode": "\nconst row = $input.first().json;\nconst r = {};\nfor (const key of Object.keys(row)) { r[key.trim()] = (row[key] ?? '').toString().trim(); }\n\nconst position   = r['Select the Position You Are Applying For'] || '';\nconst name       = r['Full Name'] || '';\nconst email      = r['Email Address'] || '';\nconst experience = r['Years of Experience'] || '';\nconst skills     = r['Relevant Skills'] || '';\nconst rowNum      = parseInt(row['row_number']) || 0;\n\nconst JD_MAP = {\n  'Frontend Developer': 'ROLE: Frontend Developer. REQUIREMENTS: 2+ years React/Vue, HTML/CSS/JavaScript.',\n  'Backend Developer': 'ROLE: Backend Developer. REQUIREMENTS: 2+ years Node.js/Python, SQL/NoSQL.',\n  'UI/UX Designer': 'ROLE: UI/UX Designer. REQUIREMENTS: Figma, Portfolio.',\n  'Project Manager': 'ROLE: Project Manager. REQUIREMENTS: 3+ years PM experience.',\n  'Digital Marketing Executive': 'ROLE: Digital Marketing Executive. REQUIREMENTS: SEO/SEM, Google Ads.'\n};\nconst jd = JD_MAP[position] || 'General professional evaluation.';\n\nreturn [{ json: { ...r, row_number: rowNum, jd } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e5b40133-c73c-4f69-b178-198c10514287",
      "name": "AI Resume Scorer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1248,
        1760
      ],
      "parameters": {
        "text": "=You are an expert HR recruiter. Score this candidate based on JD: {{ $json.jd }}. Return JSON with score (1-10), grade, strengths, weaknesses, recommendation, and summary.",
        "options": {
          "systemMessage": "HR recruiter AI. Return raw JSON only. Single line."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "e829e12d-c659-40ed-85dc-179b0f1b55c6",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1168,
        1952
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "4d8ab33b-160d-42bb-88fb-243b0b284168",
      "name": "Parse AI Output",
      "type": "n8n-nodes-base.code",
      "position": [
        1472,
        1760
      ],
      "parameters": {
        "jsCode": "const aiRaw = $input.first().json.output || '';\nconst fd = $('Extract Fields & Load JD').first().json;\nlet p = { score: 5, grade: 'Maybe' };\ntry { p = JSON.parse(aiRaw); } catch(e) {}\nconst status = p.score >= 7 ? 'Shortlisted' : 'Rejected';\nreturn [{ json: { ...fd, ...p, status, processed_at: new Date().toISOString() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "6270c4e8-eb44-4407-9b74-e760e8ddb88c",
      "name": "Build Sheet Request",
      "type": "n8n-nodes-base.code",
      "position": [
        1696,
        1760
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json;\nconst rowNum = d.row_number;\nconst range = `Form responses 1!J${rowNum}:R${rowNum}`;\nconst values = [String(d.score), d.grade, d.strengths, d.weaknesses, d.recommendation, d.summary, d.status, d.processed_at, '1'];\nreturn [{ json: { ...d, _requestBody: { range, values: [values] }, _url: `https://sheets.googleapis.com/v4/spreadsheets/1iv-8fToAzxBm8ht5vg-UP_p_hQzK4-uqs2KWu06im4o/values/${encodeURIComponent(range)}?valueInputOption=RAW` } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "aebf1e28-2ea8-4fcc-b153-9be1bcbc32d5",
      "name": "Update Sheet Row",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1920,
        1760
      ],
      "parameters": {
        "url": "={{ $json._url }}",
        "method": "PUT",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json._requestBody) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "99f3237d-7403-47d5-9c2c-21358c9fc6eb",
      "name": "Check Score \u2265 7",
      "type": "n8n-nodes-base.if",
      "position": [
        2144,
        1760
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.score }}",
              "rightValue": 7
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7dafc992-096f-4a77-932c-e2d24e465ada",
      "name": "Alert HR Team",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2368,
        1584
      ],
      "parameters": {
        "html": "Candidate scored {{ $json.score }}. Strengths: {{ $json.strengths }}",
        "options": {},
        "subject": "=Shortlist Alert: {{ $json.name }} ({{ $json.score }}/10)",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "b66f1229-c0ee-4a5f-ad5a-aa99138e012d",
      "name": "Notify Shortlisted Candidate",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2592,
        1760
      ],
      "parameters": {
        "html": "Congrats! You were shortlisted with a score of {{ $json.score }}.",
        "options": {},
        "subject": "=Interview Invitation - {{ $json.name }}",
        "toEmail": "={{ $json.email }}",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "eded8fe9-3fc2-4e85-98b7-7d4688c31da5",
      "name": "Notify Rejected Candidate",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2368,
        1952
      ],
      "parameters": {
        "html": "Thank you for applying. We won't be moving forward at this time.",
        "options": {},
        "subject": "Application Update",
        "toEmail": "={{ $json.email }}",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Wait 5s": {
      "main": [
        [
          {
            "node": "Notify Shortlisted Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read All Rows": {
      "main": [
        [
          {
            "node": "Filter Unprocessed Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Output": {
      "main": [
        [
          {
            "node": "Build Sheet Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Resume Scorer": {
      "main": [
        [
          {
            "node": "Parse AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Sheet Row": {
      "main": [
        [
          {
            "node": "Check Score \u2265 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Score \u2265 7": {
      "main": [
        [
          {
            "node": "Alert HR Team",
            "type": "main",
            "index": 0
          },
          {
            "node": "Wait 5s",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Rejected Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Sheet Request": {
      "main": [
        [
          {
            "node": "Update Sheet Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets Trigger": {
      "main": [
        [
          {
            "node": "Read All Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Unprocessed Rows": {
      "main": [
        [
          {
            "node": "Extract Fields & Load JD",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Fields & Load JD": {
      "main": [
        [
          {
            "node": "AI Resume Scorer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Resume Scorer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}