{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "31c1181c-28e2-4c2f-a002-51332b573ad1",
      "name": "Extract Applicant Fields",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueErrorOutput",
      "position": [
        1232,
        224
      ],
      "parameters": {
        "text": "=Extract applicant information from this Tally form submission:\n\n{{ JSON.stringify($json) }}\n\nReturn a JSON object with: full_name (string), email (string), role_applied (string), years_experience (number or 0 if unknown), experience_summary (string), motivation (string), submission_id (string).",
        "options": {
          "systemMessage": "You are a data extraction agent. Parse Tally form JSON into structured applicant fields.\n\nCRITICAL: Return ONLY a raw JSON object. No markdown fences, no explanation.\n\nOUTPUT SCHEMA: {\"full_name\": \"string\", \"email\": \"string\", \"role_applied\": \"string\", \"years_experience\": 0, \"experience_summary\": \"string\", \"motivation\": \"string\", \"submission_id\": \"string\"}"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "b724aed6-a46a-4c37-95dc-5a923566ed53",
      "name": "GPT-5.4 Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1216,
        384
      ],
      "parameters": {
        "model": "gpt-5.4",
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1e9522b4-34ce-4690-97c5-7f0fbb519cec",
      "name": "Parse Applicant Fields",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        208
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.output || $input.first().json.text || '';\nconst clean = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\nlet parsed;\ntry {\n  parsed = JSON.parse(clean);\n} catch (e) {\n  parsed = { full_name: '', email: '', role_applied: '', years_experience: 0, experience_summary: '', motivation: '', submission_id: '', parse_error: e.message };\n}\nreturn [{ json: { ...parsed, processed_at: new Date().toISOString() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4f520fe7-6313-4063-8bbd-6ffd6e4df7dd",
      "name": "Score Candidate",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueErrorOutput",
      "position": [
        1920,
        208
      ],
      "parameters": {
        "text": "=Score this job applicant for the role.\n\nRole applied for: {{ $json.role_applied }}\nApplicant name: {{ $json.full_name }}\nYears of experience: {{ $json.years_experience }}\nExperience summary: {{ $json.experience_summary }}\nMotivation: {{ $json.motivation }}\n\nScore the candidate 1-10 based on relevance of experience and quality of motivation. Assign grade A (8-10), B (6-7), C (4-5), D (1-3). Write a 2-sentence evaluation.",
        "options": {
          "systemMessage": "You are a recruiting screening agent. Evaluate candidates fairly based on their stated experience and motivation.\n\nCRITICAL: Return ONLY a raw JSON object. No markdown fences, no explanation.\n\nOUTPUT SCHEMA: {\"score\": 7, \"grade\": \"B\", \"evaluation\": \"2-sentence evaluation for the hiring manager\", \"key_strengths\": [\"string\"], \"key_concerns\": [\"string\"]}"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "103b8076-b89d-40d2-8113-bd3cee0f7032",
      "name": "GPT-5.4 Model 2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1920,
        400
      ],
      "parameters": {
        "model": "gpt-5.4",
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ee4da0fe-5cfb-4ff2-b54a-932f5e64d14e",
      "name": "Merge Score with Applicant",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        208
      ],
      "parameters": {
        "jsCode": "const applicant = $('Parse Applicant Fields').first().json;\nconst raw = $input.first().json.output || $input.first().json.text || '';\nconst clean = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\nlet scored;\ntry {\n  scored = JSON.parse(clean);\n} catch (e) {\n  scored = { score: 0, grade: 'D', evaluation: 'parse error', key_strengths: [], key_concerns: [] };\n}\nreturn [{ json: { ...applicant, score: scored.score, grade: scored.grade, evaluation: scored.evaluation, key_strengths: scored.key_strengths, key_concerns: scored.key_concerns } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "6f90fdda-0110-4579-b18e-ab8fe88bfa7c",
      "name": "Score 7 or Above?",
      "type": "n8n-nodes-base.if",
      "onError": "continueErrorOutput",
      "position": [
        2560,
        192
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-001",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.score }}",
              "rightValue": 7
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3d5992a8-45aa-43a6-beb2-d6af7c5efe1e",
      "name": "Send Interview Invitation",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2832,
        112
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "=Hi {{ $json.full_name }},\n\nThank you for applying for the {{ $json.role_applied }} position. We were impressed by your application and would love to invite you for an interview.\n\nOur team will be in touch shortly to schedule a time that works for you.\n\nLooking forward to speaking with you,\n\nThe Hiring Team",
        "options": {},
        "subject": "={{ 'Interview Invitation \u2014 ' + $json.role_applied }}"
      },
      "credentials": {},
      "typeVersion": 2.1
    },
    {
      "id": "02f4f8b5-04f9-49e3-9827-7a3817d3b04d",
      "name": "Send Rejection Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2832,
        304
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "=Hi {{ $json.full_name }},\n\nThank you for taking the time to apply for the {{ $json.role_applied }} position. After careful consideration, we have decided to move forward with other candidates whose experience more closely matches our current requirements.\n\nWe appreciate your interest and encourage you to apply for future openings.\n\nBest wishes,\n\nThe Hiring Team",
        "options": {},
        "subject": "={{ 'Your application for ' + $json.role_applied }}"
      },
      "credentials": {},
      "typeVersion": 2.1
    },
    {
      "id": "b2953d5c-431a-4095-99b5-b27608fd81d0",
      "name": "Log to Applications Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3312,
        208
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $('Score 7 or Above?').item.json.full_name }}",
            "role": "={{ $('Score 7 or Above?').item.json.role_applied }}",
            "email": "={{ $('Score 7 or Above?').item.json.email }}",
            "grade": "={{ $('Score 7 or Above?').item.json.grade }}",
            "score": "={{ $('Score 7 or Above?').item.json.score }}",
            "status": "={{ $json.status }}",
            "timestamp": "={{ $('Score 7 or Above?').item.json.processed_at }}",
            "evaluation": "={{ $('Score 7 or Above?').item.json.evaluation }}"
          },
          "schema": [
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "grade",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "grade",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "evaluation",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "evaluation",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "applications"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SPREADSHEET_ID"
        }
      },
      "credentials": {},
      "typeVersion": 4.7
    },
    {
      "id": "147f6426-2647-4b4d-b98f-dc96354082d4",
      "name": "Tally Trigger",
      "type": "n8n-nodes-tallyforms.tallyTrigger",
      "position": [
        1008,
        224
      ],
      "parameters": {
        "formId": "YOUR_TALLY_FORM_ID"
      },
      "credentials": {
        "tallyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9527c44c-86ca-4dce-b291-f1d8b2da34c5",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -144
      ],
      "parameters": {
        "width": 500,
        "height": 940,
        "content": "## Screen Job Applicants and Send Responses with Tally, OpenAI and Gmail\n\n### How it works\n1. A candidate submits a job application through a Tally form: name, email, role, years of experience, experience summary, and motivation.\n2. The Tally Trigger fires on each submission. Agent 1 extracts and normalises the applicant fields from the raw Tally payload.\n3. Agent 2 scores the candidate 1-10 against the role requirements and writes a 2-sentence evaluation.\n4. An IF node routes the applicant: score 7+ goes to the shortlist path, below 7 to rejection.\n5. Shortlisted candidates receive a personalised interview invitation via Gmail. Others get a polite rejection.\n6. Both paths log the applicant to a Google Sheet with score, grade, evaluation, and status.\n\n### Setup\n1. IMPORTANT: Open the Score Candidate agent and update the system prompt with the actual job requirements for the open role.\n2. Create your application form in Tally with fields: Full Name, Email, Role Applied For, Years of Experience (number), Experience Summary, Why do you want this role?\n3. Connect the Tally Trigger node and select your Tally form from the dropdown.\n4. Add your OpenAI API key under Credentials > OpenAI.\n5. Add Gmail OAuth2 credentials (the account that sends emails).\n6. Add Google Sheets OAuth2 credentials.\n7. Create a Google Sheet with a tab named exactly \"applications\" and these column headers in Row 1 (all lowercase):\n   timestamp | name | email | role | score | grade | evaluation | status\n8. Paste your Sheet ID into the Log to Applications Sheet node.\n9. Submit a test application through your Tally form to verify the full flow."
      },
      "typeVersion": 1
    },
    {
      "id": "41c710b4-5c97-4859-9b0f-d897623b3730",
      "name": "Sticky Note - Screening Pipeline",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 1552,
        "height": 744,
        "content": "## Screening Pipeline\n\nTally Trigger captures the application. Agent 1 extracts applicant fields. Agent 2 scores the candidate against role requirements and writes an evaluation."
      },
      "typeVersion": 1
    },
    {
      "id": "ca468496-eaa5-444d-a8a2-fafebeaf6236",
      "name": "Sticky Note - Routing & Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2480,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 1084,
        "height": 712,
        "content": "## Routing & Notifications\n\nIF node routes by score: 7+ gets an interview invite, below 7 gets a polite rejection. Both paths set a status and log to Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "bfdec8af-492e-4e36-a367-259f42cd1f27",
      "name": "Set Status Shortlisted",
      "type": "n8n-nodes-base.set",
      "position": [
        3056,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "dff5eae7-7cfe-4556-8f4b-5880a68c6c4e",
              "name": "status",
              "type": "string",
              "value": "Shortlisted"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f4b7aa0f-3b4a-4a9b-b2f1-cae7d444e228",
      "name": "Set Status Rejected",
      "type": "n8n-nodes-base.set",
      "position": [
        3056,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "dff5eae7-7cfe-4556-8f4b-5880a68c6c4e",
              "name": "status",
              "type": "string",
              "value": "Rejected"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "GPT-5.4 Model": {
      "ai_languageModel": [
        [
          {
            "node": "Extract Applicant Fields",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Tally Trigger": {
      "main": [
        [
          {
            "node": "Extract Applicant Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-5.4 Model 2": {
      "ai_languageModel": [
        [
          {
            "node": "Score Candidate",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Score Candidate": {
      "main": [
        [
          {
            "node": "Merge Score with Applicant",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Score with Applicant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score 7 or Above?": {
      "main": [
        [
          {
            "node": "Send Interview Invitation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Rejection Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Status Rejected": {
      "main": [
        [
          {
            "node": "Log to Applications Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Rejection Email": {
      "main": [
        [
          {
            "node": "Set Status Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Applicant Fields": {
      "main": [
        [
          {
            "node": "Score Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Status Shortlisted": {
      "main": [
        [
          {
            "node": "Log to Applications Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Applicant Fields": {
      "main": [
        [
          {
            "node": "Parse Applicant Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Interview Invitation": {
      "main": [
        [
          {
            "node": "Set Status Shortlisted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Score with Applicant": {
      "main": [
        [
          {
            "node": "Score 7 or Above?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}