{
  "id": "09recruit0000001",
  "name": "Screen candidates from Google Sheets and email a take-home assignment with AI",
  "tags": [],
  "nodes": [
    {
      "id": "overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2016,
        112
      ],
      "parameters": {
        "width": 1060,
        "height": 704,
        "content": "## AI candidate screener \u2192 take-home assignment\n\nScreen applicants for a Performance Marketing role against an expert rubric, then email the take-home only to the ones a human approves.\n\n### How it works\n- Reads applicants from a Google Sheet (name, email, resume text, answers).\n- Loads the screening rubric into an in-memory vector store (RAG), so scoring is grounded in real criteria, not vibes.\n- An AI Agent retrieves the rubric and scores each candidate 1-5 per competency, computes a weighted total, and flags shortlist yes/no with strengths and gaps.\n- Writes scores back to the Sheet marked SHORTLISTED_PENDING_APPROVAL.\n- After you approve the shortlist in the Sheet, the approved candidates are emailed the take-home assignment.\n\n### Setup\n1. Add credentials in the canvas: an LLM (OpenAI or Anthropic) + embeddings, Google Sheets, Gmail.\n2. Point the Sheet nodes at your applicants sheet; set the shortlist threshold in the config node.\n3. Edit the rubric and assignment text to your role.\n\n### Responsible use\nHuman approves before any candidate is emailed and makes the final call. The rubric forbids using protected attributes; scoring is explainable.\n\nBuilt by **nocode.expert** \u2014 done-for-you automation & AI workflows. https://nocode.expert"
      },
      "typeVersion": 1
    },
    {
      "id": "section-rag-rubric",
      "name": "Section: RAG rubric",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 664,
        "height": 396,
        "content": "## 1. Load rubric (RAG)\nPut the screening rubric in an in-memory vector store so scoring is grounded in real criteria."
      },
      "typeVersion": 1
    },
    {
      "id": "section-score-candidates",
      "name": "Section: Score candidates",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 812,
        "height": 396,
        "content": "## 2. Score each candidate\nAI Agent retrieves the rubric and scores each applicant (structured output). Swap the model for OpenAI if you prefer."
      },
      "typeVersion": 1
    },
    {
      "id": "section-approve-send",
      "name": "Section: Approve & send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1552,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 1036,
        "height": 396,
        "content": "## 3. Approve \u2192 send\nWrite scores to the Sheet, you approve the shortlist, then email the take-home to approved candidates."
      },
      "typeVersion": 1
    },
    {
      "id": "run-manually",
      "name": "Run manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -768,
        464
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "set-config-rubric",
      "name": "Set config + rubric",
      "type": "n8n-nodes-base.set",
      "position": [
        -480,
        464
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "rubricText",
              "type": "string",
              "value": "PERFORMANCE MARKETING \u2014 CANDIDATE SCREENING RUBRIC\n\nScore each competency 1-5 (5=exceptional). Shortlist only if weighted score >= threshold AND all MUST-HAVES are met.\n\n1. PAID MEDIA EXECUTION (weight 30%) \u2014 Hands-on build/optimize/scale on Meta and Google (TikTok a plus). Evidence of managing real budgets, not just dashboards. MUST-HAVE: has personally run paid campaigns end to end.\n2. MEASUREMENT & TRACKING (weight 25%) \u2014 Understands attribution, server-side conversions/CAPI, ROAS/CAC/MER, and why platform-reported numbers lie. MUST-HAVE: can explain how they verify a conversion is real.\n3. TESTING & CREATIVE (weight 20%) \u2014 Hypothesis-driven testing, structured creative iteration, reads results without fooling themselves. \n4. SCALING & BUDGET JUDGMENT (weight 15%) \u2014 Knows how to scale efficiently and when to cut; treats spend as their own money.\n5. OWNERSHIP & COMMUNICATION (weight 10%) \u2014 Clear reporting, takes accountability for outcomes, explains tradeoffs simply.\n\nRED FLAGS: only agency-side reporting with no execution; conflates clicks/impressions with conversions; no measurement rigor; blames platforms for everything.\nDo NOT consider or infer any protected attribute (age, gender, race, nationality, etc.). Score only on evidence of competency."
            },
            {
              "id": "a2",
              "name": "threshold",
              "type": "string",
              "value": "70"
            },
            {
              "id": "a3",
              "name": "assignmentSubject",
              "type": "string",
              "value": "Take-home for the Performance Marketing role"
            },
            {
              "id": "a4",
              "name": "assignmentBrief",
              "type": "string",
              "value": "Using the sample account data attached, identify the 3 biggest opportunities, write a 1-week action plan, and propose a measurement setup you would trust. Keep it under 2 hours. Reply to this email with your submission."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "rubric-document-loader",
      "name": "Rubric document loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        432,
        1056
      ],
      "parameters": {
        "options": {},
        "jsonData": "={{ $json.rubricText }}",
        "jsonMode": "expressionData"
      },
      "typeVersion": 1.1
    },
    {
      "id": "embeddings",
      "name": "Embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "position": [
        -80,
        1264
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "load-rubric-into-vector-store",
      "name": "Load rubric into vector store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory",
      "position": [
        -160,
        464
      ],
      "parameters": {
        "mode": "insert",
        "memoryKey": "pm_screening_rubric"
      },
      "typeVersion": 1.1
    },
    {
      "id": "get-candidates-from-google-sheet",
      "name": "Get candidates from Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        208,
        464
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "rubric-retriever-tool",
      "name": "Rubric retriever (tool)",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory",
      "position": [
        1136,
        1152
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "toolName": "pm_rubric",
        "memoryKey": "pm_screening_rubric",
        "toolDescription": "The performance marketing screening rubric and scoring criteria"
      },
      "typeVersion": 1.1
    },
    {
      "id": "anthropic-chat-model",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        720,
        896
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "options": {}
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "structured-score-parser",
      "name": "Structured score parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1376,
        976
      ],
      "parameters": {
        "jsonSchemaExample": "{\"weighted_total\":82,\"shortlist\":true,\"strengths\":[\"ran real Meta budgets\",\"explains CAPI verification\"],\"gaps\":[\"light on TikTok\"]}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "score-candidate-vs-rubric",
      "name": "Score candidate vs rubric",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        864,
        464
      ],
      "parameters": {
        "text": "You are screening a candidate for a Performance Marketing role. Use the pm_rubric tool to retrieve the scoring criteria, then evaluate ONLY the candidate evidence below against it.\nScore each of the 5 competencies 1-5 with a one-line justification, compute the weighted total (0-100), decide shortlist true/false (must-haves met AND weighted >= threshold), and list up to 3 strengths and 3 gaps.\nNever infer or use protected attributes. Be specific and terse.\n\nCandidate: {{ $json.name }} <{{ $json.email }}>\nResume: {{ $json.resume_text }}\nApplication answers: {{ $json.answers }}\nShortlist threshold: {{ $('Set config + rubric').first().json.threshold }}",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.9
    },
    {
      "id": "flatten-scores-for-sheet",
      "name": "Flatten scores for Sheet",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        464
      ],
      "parameters": {
        "jsCode": "// Normalize the agent's structured output into flat columns for the Sheet.\nconst candidates = $('Get candidates from Google Sheet').all();\nconst items = $input.all();\nconst out = [];\nfor (let i = 0; i < items.length; i++) {\n  const j = items[i].json;\n  const r = j.output || j; // agent output may be nested\n  const cand = (candidates[i] && candidates[i].json) || {};\n  out.push({ json: {\n    name: cand.name,\n    email: cand.email,\n    weighted_score: r.weighted_total ?? r.score ?? null,\n    shortlist: !!r.shortlist,\n    strengths: Array.isArray(r.strengths) ? r.strengths.join('; ') : (r.strengths || ''),\n    gaps: Array.isArray(r.gaps) ? r.gaps.join('; ') : (r.gaps || ''),\n    status: r.shortlist ? 'SHORTLISTED_PENDING_APPROVAL' : 'REJECTED',\n  } });\n}\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "write-scores-back-to-sheet",
      "name": "Write scores back to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1696,
        464
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "get-approved-shortlist",
      "name": "Get approved shortlist",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2128,
        464
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "email-the-take-home-assignment",
      "name": "Email the take-home assignment",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2864,
        192
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "=Hi {{ $json.name }},\n\n{{ $('Set config + rubric').first().json.assignmentBrief }}\n\nThanks!",
        "options": {},
        "subject": "={{ $('Set config + rubric').first().json.assignmentSubject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "c9c02279-dee1-4694-8169-4cff40cf2c73",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        2384,
        464
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Shortlisted",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "1e2b599d-0aa0-471b-a599-a097b947156d",
                    "operator": {
                      "type": "string",
                      "operation": "notEquals"
                    },
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "REJECTED"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Not Shortlisted",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8c5840a0-ba2f-41d7-b998-4df1be571e8d",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "REJECTED"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "tpBgHHbxFDn722Pt",
      "name": "Fix score format",
      "type": "@n8n/n8n-nodes-langchain.outputParserAutofixing",
      "position": [
        1328,
        800
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "4ad937a5-8b5f-4131-a8b4-4dca8f072196",
      "name": "Human evaluation",
      "type": "n8n-nodes-base.gmail",
      "disabled": true,
      "position": [
        2880,
        640
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "=Hi {{ $json.name }},\n\n{{ $('Set config + rubric').first().json.assignmentBrief }}\n\nThanks!",
        "options": {},
        "subject": "={{ $('Set config + rubric').first().json.assignmentSubject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "24077f86-3f28-46ea-a1f7-c57b696b189c",
  "nodeGroups": [],
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Email the take-home assignment",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Human evaluation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings": {
      "ai_embedding": [
        [
          {
            "node": "Load rubric into vector store",
            "type": "ai_embedding",
            "index": 0
          },
          {
            "node": "Rubric retriever (tool)",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Run manually": {
      "main": [
        [
          {
            "node": "Set config + rubric",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fix score format": {
      "ai_outputParser": [
        [
          {
            "node": "Score candidate vs rubric",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Set config + rubric": {
      "main": [
        [
          {
            "node": "Load rubric into vector store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Score candidate vs rubric",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Fix score format",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get approved shortlist": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rubric document loader": {
      "ai_document": [
        [
          {
            "node": "Load rubric into vector store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Rubric retriever (tool)": {
      "ai_tool": [
        [
          {
            "node": "Score candidate vs rubric",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Structured score parser": {
      "ai_outputParser": [
        [
          {
            "node": "Fix score format",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Flatten scores for Sheet": {
      "main": [
        [
          {
            "node": "Write scores back to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score candidate vs rubric": {
      "main": [
        [
          {
            "node": "Flatten scores for Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write scores back to Sheet": {
      "main": [
        [
          {
            "node": "Get approved shortlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load rubric into vector store": {
      "main": [
        [
          {
            "node": "Get candidates from Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get candidates from Google Sheet": {
      "main": [
        [
          {
            "node": "Score candidate vs rubric",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}