This workflow corresponds to n8n.io template #16286 — 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 →
{
"id": "R4jO9VGJTM5xxyqk",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Interview Brief Generator",
"tags": [],
"nodes": [
{
"id": "5587625c-4a1a-4c79-9fc5-9ef94af79cbb",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1008,
784
],
"parameters": {
"color": 4,
"width": 668,
"height": 1252,
"content": "## AI Interview Brief Generator \u2014 Resume PDF + GPT-4o-mini + Google Drive + Google Sheets + Gmail\n\nFor HR teams and hiring managers who want instant, AI-generated interviewer brief packs for every candidate \u2014 without manual resume reading. Add candidate rows to a Google Sheet (Name, Email, Job Role, Resume Drive Link, Interviewer Email) and the workflow runs automatically on a schedule. For each unprocessed row: the resume PDF is downloaded from Google Drive, text is extracted, and GPT-4o-mini generates a complete HTML interviewer brief with candidate summary, job fit score, experience highlights, red flags, 7 targeted interview questions, suggested follow-up probes, and a final recommendation. The brief is emailed directly to the interviewer. The sheet row is marked Processed to prevent duplicates. A 5-second wait between candidates prevents rate limiting.\n\n## How it works\n- **Schedule Trigger** fires on your configured interval (default every 15 minutes)\n- **1. Google Sheets \u2014 Read Candidates** reads all rows from the Candidates tab\n- **2. SplitInBatches \u2014 One at a Time** processes one candidate row per cycle\n- **3. IF \u2014 Skip Processed Rows** checks Status column \u2014 skips rows already marked Processed\n- **4. Code \u2014 Extract Drive File ID** validates required fields and extracts the Google Drive file ID from the full URL or raw ID string\n- **5. Google Drive \u2014 Download PDF** downloads the resume PDF binary using the file ID\n- **6. Extract From File \u2014 PDF to Text** extracts plain text from the PDF binary\n- **7. Code \u2014 Merge Resume and Metadata** cleans the extracted text, trims to 4000 characters, and merges with candidate metadata\n- **8. AI Agent \u2014 Generate Interview Brief** uses GPT-4o-mini to generate a complete HTML interviewer brief\n- **OpenAI \u2014 GPT-4o-mini Model** language model attached to the AI Agent\n- **9. Gmail \u2014 Send Brief to Interviewer** sends the HTML brief to the interviewer email from the sheet row\n- **10. Google Sheets \u2014 Mark as Processed** updates the Status column to Processed using row_number as the match key\n- **11. Wait \u2014 5 Seconds** pauses before looping back to process the next candidate\n\n## Set up steps\n1. In **1. Google Sheets \u2014 Read Candidates** and **10. Google Sheets \u2014 Mark as Processed** \u2014 connect Google Sheets OAuth2 credential and replace `YOUR_CANDIDATES_SHEET_ID`. Create a tab named Candidates with columns: Name, Email, Job Role, Resume Drive Link, Interviewer Email, Status\n2. In **5. Google Drive \u2014 Download PDF** \u2014 connect Google Drive OAuth2 credential. Each resume PDF in the sheet must be shared as Anyone with link can view\n3. In **OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI API credential\n4. In **9. Gmail \u2014 Send Brief to Interviewer** \u2014 connect Gmail OAuth2 credential\n5. Set the Schedule Trigger interval to match how often you want to check for new candidates"
},
"typeVersion": 1
},
{
"id": "024eab4a-ca95-4a6a-a52c-250d4b76d8eb",
"name": "Section \u2014 Schedule Trigger and Candidate Sheet Read",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
1136
],
"parameters": {
"color": 5,
"width": 788,
"height": 372,
"content": "## Schedule Trigger and Candidate Sheet Read\nSchedule fires on your configured interval. Sheets reads all rows from the Candidates tab \u2014 SplitInBatches then processes them one at a time."
},
"typeVersion": 1
},
{
"id": "f73309a4-9e9e-4e7a-9c42-126375d56493",
"name": "Section \u2014 Per-Candidate Split and Processed Row Skip",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
1136
],
"parameters": {
"color": 6,
"width": 516,
"height": 484,
"content": "## Per-Candidate Split and Processed Row Skip\nSplitInBatches loops one candidate at a time. IF node checks the Status column \u2014 rows with Status = Processed are skipped and loop back via the Wait node."
},
"typeVersion": 1
},
{
"id": "0d02a243-b164-4794-a116-5a6a9c5a75f6",
"name": "Section \u2014 Drive ID Extract, PDF Download, Text Extraction, and Resume Merge",
"type": "n8n-nodes-base.stickyNote",
"position": [
1216,
1184
],
"parameters": {
"color": 6,
"width": 900,
"height": 596,
"content": "## Drive ID Extract, PDF Download, Text Extraction, and Resume Merge\nCode extracts the Drive file ID from full URL or raw ID. Drive downloads the PDF binary. Extract From File converts to plain text. Code cleans and trims to 4000 characters and merges with candidate metadata."
},
"typeVersion": 1
},
{
"id": "ba977436-971b-4c7c-8ff7-652ad5bcefd1",
"name": "Section \u2014 GPT-4o-mini Interview Brief Generation",
"type": "n8n-nodes-base.stickyNote",
"position": [
2176,
960
],
"parameters": {
"color": 6,
"width": 404,
"height": 868,
"content": "## GPT-4o-mini Interview Brief Generation\nGPT-4o-mini reads the resume text and candidate metadata and generates a complete HTML interviewer brief with summary, fit score, experience highlights, red flags, 7 questions, follow-up probes, and recommendation."
},
"typeVersion": 1
},
{
"id": "5ce43b9d-fa5c-4c56-83be-5111cd8ee1f9",
"name": "Section \u2014 Gmail Send, Sheets Update, and Loop Wait",
"type": "n8n-nodes-base.stickyNote",
"position": [
2656,
1024
],
"parameters": {
"color": 4,
"width": 532,
"height": 740,
"content": "## Gmail Send, Sheets Update, and Loop Wait\nGmail and Sheets run in parallel from node 8. Gmail sends the HTML brief to the interviewer. Sheets marks the row as Processed. Wait 5 seconds then loops back to SplitInBatches for the next candidate."
},
"typeVersion": 1
},
{
"id": "ab01a689-d564-49df-a2b0-af28b4a72396",
"name": "Schedule Trigger \u2014 Every 15 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-144,
1328
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
},
"typeVersion": 1.3
},
{
"id": "a19783bf-fa42-49a5-a48e-b39cad13c09b",
"name": "1. Google Sheets \u2014 Read Candidates",
"type": "n8n-nodes-base.googleSheets",
"position": [
208,
1328
],
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_CANDIDATES_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "3e266f33-ed7c-426b-bbba-a60055d4c88a",
"name": "2. SplitInBatches \u2014 One at a Time",
"type": "n8n-nodes-base.splitInBatches",
"position": [
784,
1328
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "87136eb1-4f04-411f-9008-95220a62cf3d",
"name": "3. IF \u2014 Skip Processed Rows",
"type": "n8n-nodes-base.if",
"position": [
1024,
1328
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "status-check",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json['Status'] }}",
"rightValue": "Processed"
}
]
}
},
"typeVersion": 2
},
{
"id": "6410d7ba-9fad-403e-ad67-f41ed1a62b1f",
"name": "4. Code \u2014 Extract Drive File ID",
"type": "n8n-nodes-base.code",
"position": [
1264,
1328
],
"parameters": {
"jsCode": "const row = $json;\n\nconst name = (row['Name'] || '').trim();\nconst email = (row['Email'] || '').trim();\nconst jobRole = (row['Job Role'] || '').trim();\nconst resumeLink = (row['Resume Drive Link'] || '').trim();\nconst interviewerEmail = (row['Interviewer Email'] || email).trim();\nconst rowNumber = row.row_number;\n\nif (!name || !resumeLink) {\n throw new Error(`Row ${rowNumber}: Name and Resume Drive Link are required.`);\n}\n\nlet fileId = resumeLink;\nconst matchFileD = resumeLink.match(/\\/file\\/d\\/([a-zA-Z0-9_-]+)/);\nconst matchOpenId = resumeLink.match(/[?&]id=([a-zA-Z0-9_-]+)/);\nif (matchFileD) fileId = matchFileD[1];\nelse if (matchOpenId) fileId = matchOpenId[1];\n\nreturn [{\n json: {\n name, email, jobRole, resumeLink,\n interviewerEmail, rowNumber, fileId\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7008d191-f693-4aaf-a363-b1ed9dd0eeaf",
"name": "5. Google Drive \u2014 Download PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
1504,
1328
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.fileId }}"
},
"options": {},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "7dee7aa9-458c-4f47-9c64-8776d1c1730e",
"name": "6. Extract From File \u2014 PDF to Text",
"type": "n8n-nodes-base.extractFromFile",
"position": [
1744,
1328
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "41cd9821-107e-432b-99c6-b0f791c2b648",
"name": "7. Code \u2014 Merge Resume and Metadata",
"type": "n8n-nodes-base.code",
"position": [
1984,
1328
],
"parameters": {
"jsCode": "const resumeData = $json;\nconst candidateData = $('4. Code \u2014 Extract Drive File ID').item.json;\n\nlet resumeText = '';\nif (typeof resumeData.text === 'string') resumeText = resumeData.text;\nelse if (typeof resumeData.data === 'string') resumeText = resumeData.data;\nelse resumeText = JSON.stringify(resumeData);\n\nresumeText = resumeText\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ ]{3,}/g, ' ')\n .trim();\n\nif (resumeText.length > 4000) {\n resumeText = resumeText.substring(0, 4000) + '\\n[Resume truncated \u2014 first 4000 characters shown]';\n}\n\nif (!resumeText || resumeText.length < 50) {\n throw new Error(\n `Empty resume text for ${candidateData.name}. ` +\n 'Ensure the PDF is text-based (not a scanned image).'\n );\n}\n\nreturn [{\n json: {\n resumeText,\n name: candidateData.name,\n email: candidateData.email,\n jobRole: candidateData.jobRole,\n interviewerEmail: candidateData.interviewerEmail,\n rowNumber: candidateData.rowNumber,\n fileId: candidateData.fileId\n }\n}];"
},
"typeVersion": 2
},
{
"id": "cc99b50d-1734-475a-aa0c-f884972a2c82",
"name": "8. AI Agent \u2014 Generate Interview Brief",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2256,
1328
],
"parameters": {
"text": "=CANDIDATE NAME: {{ $json.name }}\nAPPLIED FOR: {{ $json.jobRole }}\nCANDIDATE EMAIL: {{ $json.email }}\n\nRESUME TEXT:\n{{ $json.resumeText }}",
"options": {
"systemMessage": "You are a senior HR consultant and expert interviewer. Read the candidate resume and generate a complete HTML interviewer brief pack.\n\nReturn ONLY valid HTML with embedded CSS \u2014 no markdown, no code fences, no explanation.\n\nHTML STRUCTURE TO USE:\n\n<!DOCTYPE html><html><head><meta charset='UTF-8'><style>body{font-family:Arial,sans-serif;max-width:800px;margin:0 auto;background:#f5f5f5;padding:20px;}.header{background:#1a1a2e;color:#fff;padding:28px;border-radius:8px 8px 0 0;}.header h1{margin:0;font-size:22px;}.header p{margin:6px 0 0;color:#aaa;font-size:13px;}.score-bar{background:#4a90e2;padding:10px 24px;}.score-bar p{color:#fff;font-size:13px;margin:0;}.section{background:#fff;padding:24px;border-bottom:1px solid #eee;}.section h2{font-size:16px;color:#1a1a2e;border-left:4px solid #4a90e2;padding-left:10px;margin-top:0;}.badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:12px;font-weight:bold;margin:2px;}.green{background:#e8f5e9;color:#2e7d32;}.red{background:#ffebee;color:#c62828;}.yellow{background:#fff8e1;color:#f57f17;}.question{background:#f0f4ff;border-left:3px solid #4a90e2;padding:12px;margin:8px 0;border-radius:0 6px 6px 0;font-size:14px;color:#333;}.followup{background:#f9f9f9;padding:8px 12px;font-size:13px;color:#555;border-left:2px solid #ccc;margin:4px 0;}.footer{background:#1a1a2e;color:#888;padding:14px 24px;text-align:center;font-size:11px;border-radius:0 0 8px 8px;}</style></head><body>\n<div class='header'><h1>Interviewer Brief Pack</h1><p>[CANDIDATE NAME] \u2014 [JOB ROLE]</p></div>\n<div class='score-bar'><p>Job Fit Score: [X/10] | Experience: [X years] | Notice Period: [if mentioned]</p></div>\n<div class='section'><h2>Candidate Summary</h2><p>[3-4 sentence summary: background, strengths, career trajectory]</p></div>\n<div class='section'><h2>Job Fit Analysis</h2><p><strong>Strengths:</strong></p><span class='badge green'>[Strength 1]</span><span class='badge green'>[Strength 2]</span><span class='badge green'>[Strength 3]</span><p style='margin-top:12px'><strong>Gaps:</strong></p><span class='badge red'>[Gap 1]</span><span class='badge yellow'>[Gap 2]</span></div>\n<div class='section'><h2>Experience Highlights</h2><ul><li>[Most relevant achievement]</li><li>[Second most relevant]</li><li>[Third most relevant]</li></ul></div>\n<div class='section'><h2>Red Flags to Probe</h2><ul><li>[Flag 1 \u2014 e.g. employment gap, frequent job changes]</li><li>[Flag 2]</li></ul><p style='font-size:13px;color:#666'>These are areas to clarify \u2014 not automatic disqualifiers.</p></div>\n<div class='section'><h2>7 Targeted Interview Questions</h2><div class='question'>1. [Question specific to candidate background and role]</div><div class='question'>2. [Question about a specific achievement on their resume]</div><div class='question'>3. [Behavioral question relevant to the role]</div><div class='question'>4. [Question probing a red flag or gap]</div><div class='question'>5. [Technical or skills-based question]</div><div class='question'>6. [Culture fit or motivation question]</div><div class='question'>7. [Future goals and alignment question]</div></div>\n<div class='section'><h2>Suggested Follow-up Probes</h2><div class='followup'>- [Follow-up if candidate gives vague answer to Q1]</div><div class='followup'>- [Follow-up to dig deeper on experience]</div><div class='followup'>- [Follow-up on red flag]</div></div>\n<div class='section'><h2>Interviewer Recommendation</h2><p>[One paragraph: overall impression, whether to proceed, what to focus on in the interview]</p></div>\n<div class='footer'>Generated by AI Interview Brief System \u2014 Powered by n8n + GPT-4o-mini</div></body></html>"
},
"promptType": "define"
},
"typeVersion": 1.9
},
{
"id": "673abe34-570e-41fa-98c9-6e0399d14977",
"name": "OpenAI \u2014 GPT-4o-mini Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2256,
1616
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "8f634bf6-e353-4cdc-ad92-b5c03492c632",
"name": "9. Gmail \u2014 Send Brief to Interviewer",
"type": "n8n-nodes-base.gmail",
"position": [
2800,
1296
],
"parameters": {
"sendTo": "={{ $('7. Code \u2014 Merge Resume and Metadata').item.json.interviewerEmail }}",
"message": "={{ $json.output }}",
"options": {
"senderName": "AI Hiring Assistant",
"appendAttribution": false
},
"subject": "=Interview Brief \u2014 {{ $('7. Code \u2014 Merge Resume and Metadata').item.json.name }} | {{ $('7. Code \u2014 Merge Resume and Metadata').item.json.jobRole }}"
},
"typeVersion": 2.1
},
{
"id": "69ed78b6-e909-4374-aaba-6c3f448bf2ba",
"name": "10. Google Sheets \u2014 Mark as Processed",
"type": "n8n-nodes-base.googleSheets",
"position": [
2800,
1488
],
"parameters": {
"columns": {
"value": {
"Status": "Processed",
"row_number": "={{ $('7. Code \u2014 Merge Resume and Metadata').item.json.rowNumber }}"
},
"schema": [
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"row_number"
]
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Candidates"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_CANDIDATES_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "488a93b6-afde-4b91-ac32-ed67b584f82f",
"name": "11. Wait \u2014 5 Seconds",
"type": "n8n-nodes-base.wait",
"position": [
3040,
1392
],
"parameters": {},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "a9ec758f-c667-47ac-98ed-309ae33ee05b",
"nodeGroups": [],
"connections": {
"11. Wait \u2014 5 Seconds": {
"main": [
[
{
"node": "2. SplitInBatches \u2014 One at a Time",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2014 GPT-4o-mini Model": {
"ai_languageModel": [
[
{
"node": "8. AI Agent \u2014 Generate Interview Brief",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"3. IF \u2014 Skip Processed Rows": {
"main": [
[
{
"node": "4. Code \u2014 Extract Drive File ID",
"type": "main",
"index": 0
}
],
[
{
"node": "11. Wait \u2014 5 Seconds",
"type": "main",
"index": 0
}
]
]
},
"5. Google Drive \u2014 Download PDF": {
"main": [
[
{
"node": "6. Extract From File \u2014 PDF to Text",
"type": "main",
"index": 0
}
]
]
},
"4. Code \u2014 Extract Drive File ID": {
"main": [
[
{
"node": "5. Google Drive \u2014 Download PDF",
"type": "main",
"index": 0
}
]
]
},
"2. SplitInBatches \u2014 One at a Time": {
"main": [
[],
[
{
"node": "3. IF \u2014 Skip Processed Rows",
"type": "main",
"index": 0
}
]
]
},
"1. Google Sheets \u2014 Read Candidates": {
"main": [
[
{
"node": "2. SplitInBatches \u2014 One at a Time",
"type": "main",
"index": 0
}
]
]
},
"6. Extract From File \u2014 PDF to Text": {
"main": [
[
{
"node": "7. Code \u2014 Merge Resume and Metadata",
"type": "main",
"index": 0
}
]
]
},
"7. Code \u2014 Merge Resume and Metadata": {
"main": [
[
{
"node": "8. AI Agent \u2014 Generate Interview Brief",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger \u2014 Every 15 Minutes": {
"main": [
[
{
"node": "1. Google Sheets \u2014 Read Candidates",
"type": "main",
"index": 0
}
]
]
},
"9. Gmail \u2014 Send Brief to Interviewer": {
"main": [
[
{
"node": "11. Wait \u2014 5 Seconds",
"type": "main",
"index": 0
}
]
]
},
"10. Google Sheets \u2014 Mark as Processed": {
"main": [
[
{
"node": "11. Wait \u2014 5 Seconds",
"type": "main",
"index": 0
}
]
]
},
"8. AI Agent \u2014 Generate Interview Brief": {
"main": [
[
{
"node": "9. Gmail \u2014 Send Brief to Interviewer",
"type": "main",
"index": 0
},
{
"node": "10. Google Sheets \u2014 Mark as Processed",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every 15 minutes to read candidate rows from Google Sheets, download each resume PDF from Google Drive, extract text, generate an HTML interview brief using OpenAI GPT-4o-mini, email it via Gmail to the interviewer, and mark the row as Processed to avoid…
Source: https://n8n.io/workflows/16286/ — 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 n8n automation workflow automates the creation, scripting, production, and posting of YouTube videos. It leverages AI (OpenAI), image generation (PIAPI), video rendering (Shotstack), and platform
The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe
This workflow automates the creation, rendering, approval, and posting of TikTok-style POV (Point of View) videos to Instagram, with cross-posting to Facebook and YouTube. It eliminates manual video p
Note: This template is for self-hosted n8n instances only
Automates monthly payroll processing and tax compliance by calculating employee payroll, applying accurate withholdings, generating comprehensive tax summaries, and producing compliance-ready document