This workflow corresponds to n8n.io template #15057 — we link there as the canonical source.
This workflow follows the Agent → 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 →
{
"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
}
]
]
}
}
}
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.
openAiApitallyApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically screens job applicants, scores them against role requirements, sends personalized emails (interview invitation or polite rejection), and logs everything to a Google Sheet. It replaces hours of manual resume triage per hiring cycle.
Source: https://n8n.io/workflows/15057/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is designed for Scrum Masters and Agile Coaches who prepare and coordinate Sprint Planning sessions, using Google Calendar, Google Sheets, and OpenAI.
This workflow is designed for Scrum Masters, Agile Coaches, and Product Owners who want to automate backlog refinement preparation using Google Sheets, Gmail, and OpenAI. It’s ideal for teams seeking
Automated Book Generation System. Uses googleSheetsTrigger, gmail, lmChatOpenAi, outputParserStructured. Event-driven trigger; 43 nodes.
This n8n workflow template automates your lead generation and follow-up process using AI. It captures leads through a form, enriches them with company data, classifies them into different categories,
Want to skip the manual work and instantly generate SWOT analyses for your business plans, investor decks, or strategy docs? 🚀 This workflow lets you automate the entire SWOT (Strengths, Weaknesses, O