This workflow follows the Chainllm → 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": "MGgeJBbeovHVQAbq",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Daily-Applicant-Digest",
"tags": [],
"nodes": [
{
"id": "75fe46c9-d9eb-455e-8f9d-7384c4a1d172",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-340,
-80
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 6
}
]
}
},
"typeVersion": 1.2
},
{
"id": "ba41cb19-9463-4c8a-a94d-a7d579346b1e",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
120,
120
],
"parameters": {
"options": {},
"modelName": "models/gemini-1.5-flash"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "9e2ef974-5183-4e7d-9cd2-8f3872bd0a66",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-520,
-360
],
"parameters": {
"width": 1720,
"height": 680,
"content": "## Send daily applicant digest by role from Gmail to hiring managers with Google Gemini"
},
"typeVersion": 1
},
{
"id": "4648ef51-49af-40f8-9c82-490c5965e5be",
"name": "fetch_applicant_emails",
"type": "n8n-nodes-base.gmail",
"position": [
-120,
-80
],
"parameters": {
"filters": {
"q": "label: applicant newer_than:1d is:unread"
},
"operation": "getAll"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "f479ae8a-490b-4b08-9fe3-dbe6a26b2320",
"name": "readAll_applicant_emails",
"type": "n8n-nodes-base.gmail",
"position": [
120,
-280
],
"parameters": {
"messageId": "={{ $json.id }}",
"operation": "markAsRead"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "2a8e9656-df75-439f-ac16-ba608b29c87d",
"name": "Extract Applicant Details",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
100,
-80
],
"parameters": {
"text": "=You are an assistant that extracts job applicant information from emails.\n\nExtract the following fields and return ONLY valid JSON with these keys:\n- name\n- email\n- phone\n- role\n- years_of_experience\n- top_skills\n- location\n- notice_period\n- summary\n\nApplicant email:\n\"\"\"\n{{$json.snippet }}\n\"\"\"\n",
"batching": {},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "3937cc5c-ea58-4b5d-b22d-591f8097481f",
"name": "Assign Manager Emails",
"type": "n8n-nodes-base.code",
"position": [
480,
-80
],
"parameters": {
"jsCode": "function stripMarkdownJson(input) {\n let str = (input || '').trim();\n\n // Remove the ```json at the start and ```\n // This regex removes triple backticks and the 'json' keyword at the start\n str = str.replace(/^```json\\s*/, ''); // Remove starting ```\n str = str.replace(/```$/, ''); // Remove ending ```\n \n return str.trim();\n}\n\nfunction normalizeRole(role) {\n return (role || '').trim().toLowerCase();\n}\n\nconst roleToManagerEmail = {\n 'java team lead': 'user@example.com',\n 'python developer': 'user@example.com',\n 'frontend developer': 'user@example.com',\n // add other roles as needed\n};\n\nconst fallbackManagerEmail = 'user@example.com';\n\nreturn items.map(item => {\n const raw = item.json.text || '';\n const jsonStr = stripMarkdownJson(raw);\n\n let parsed;\n try {\n parsed = JSON.parse(jsonStr);\n } catch (e) {\n item.json.error = `JSON parse failed: ${e.message}`;\n return item;\n }\n\n // Merge the parsed JSON fields into the current item\n Object.assign(item.json, parsed);\n\n // Normalize role and assign manager email\n const normalizedRole = normalizeRole(item.json.role);\n const managerEmail = Object.prototype.hasOwnProperty.call(roleToManagerEmail, normalizedRole)\n ? roleToManagerEmail[normalizedRole]\n : fallbackManagerEmail;\n\n item.json.managerEmail = managerEmail;\n\n return item;\n});\n"
},
"typeVersion": 2
},
{
"id": "5943f68f-a39f-4532-a437-732d98d53079",
"name": "Group & Build HTML Tables",
"type": "n8n-nodes-base.code",
"position": [
700,
-80
],
"parameters": {
"jsCode": "function escapeHtml(text) {\n if (!text) return '';\n return text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n// Group applicants by managerEmail, then by role\nconst groupedByManager = {};\n\n// Step 1: Group items\nitems.forEach(item => {\n const applicant = item.json;\n const managerEmail = applicant.managerEmail || 'user@example.com';\n const role = applicant.role || 'Unknown Role';\n\n if (!groupedByManager[managerEmail]) {\n groupedByManager[managerEmail] = {};\n }\n if (!groupedByManager[managerEmail][role]) {\n groupedByManager[managerEmail][role] = [];\n }\n \n groupedByManager[managerEmail][role].push(applicant);\n});\n\n// Step 2: Build HTML per manager with tables per role\nconst output = [];\n\nfor (const [managerEmail, roles] of Object.entries(groupedByManager)) {\n\n let emailHtml = `<h2>Today's New Applicants</h2>`;\n\n for (const [role, applicants] of Object.entries(roles)) {\n emailHtml += `<h3>Role: ${escapeHtml(role)}</h3>`;\n \n // Build table header\n emailHtml += `\n <table border=\"1\" cellpadding=\"5\" cellspacing=\"0\" style=\"border-collapse:collapse; width: 100%;\">\n <thead style=\"background-color:#f0f0f0;\">\n <tr>\n <th>Name</th>\n <th>Email</th>\n <th>Phone</th>\n <th>Years of Experience</th>\n <th>Top Skills</th>\n <th>Location</th>\n <th>Notice Period</th>\n <th>Summary</th>\n </tr>\n </thead>\n <tbody>\n `;\n\n // Add each applicant as a row\n applicants.forEach(app => {\n emailHtml += `\n <tr>\n <td>${escapeHtml(app.name)}</td>\n <td><a href=\"mailto:${escapeHtml(app.email)}\">${escapeHtml(app.email)}</a></td>\n <td>${escapeHtml(app.phone)}</td>\n <td>${escapeHtml(app.years_of_experience?.toString())}</td>\n <td>${Array.isArray(app.top_skills) ? escapeHtml(app.top_skills.join(', ')) : escapeHtml(app.top_skills)}</td>\n <td>${escapeHtml(app.location)}</td>\n <td>${escapeHtml(app.notice_period)}</td>\n <td>${escapeHtml(app.summary)}</td>\n </tr>\n `;\n });\n\n emailHtml += `</tbody></table><br/>`;\n }\n\n // Push one item per manager, as output for the next step (email sending)\n output.push({\n json: {\n managerEmail,\n html: emailHtml\n }\n });\n}\n\nreturn output;\n"
},
"typeVersion": 2
},
{
"id": "9509d5c2-f66f-4294-9432-0b2587283b7a",
"name": "Send Digest to Managers",
"type": "n8n-nodes-base.gmail",
"position": [
900,
-80
],
"parameters": {
"sendTo": "={{ $json.managerEmail }}",
"message": "={{ $json.html }}",
"options": {},
"subject": "=Today's Applicants Digest \u2013 {{ $now.format('MM-DD') }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "17486203-2374-43f0-88eb-80500108d770",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-520,
360
],
"parameters": {
"width": 1720,
"height": 780,
"content": "## Workflow Overview: Send daily applicant digest by role from Gmail to hiring managers with Google Gemini\n\n**Purpose:**\nAutomatically fetches new job application emails labeled applicants, extracts structured applicant details using OpenAI, groups candidates by role and manager, then sends a daily HTML summary email to each hiring manager.\n\nDaily Applicant Digest Workflow \u2013 Node Overview\n\n**1. Daily Trigger (6PM IST)**\n\nStarts the workflow every day at 18:00 (Asia/Kolkata timezone).\n\n**2. Fetch Applicant Emails**\n\nRetrieves all new application emails labeled applicants from the last 24 hours.\n\n**3. Read All Emails**\n\nRead each email\u2019s labeled applicants which we retrieves\n\n**4. Extract Applicant Details**\n\nUses OpenAI to extract and structure applicant info (name, email, role, skills, etc.) in JSON format.\n\n**5. Assign Manager Emails**\n\nMaps each applicant\u2019s role to a hiring manager\u2019s email address.\n\nUses a fallback email if the role does not match any manager.\n\n**6. Group & Build HTML Tables**\n\nGroups applicants by manager and role.\n\nBuilds a clear, formatted HTML table for each group, summarizing all applicants.\n\n**7. Send Digest to Managers**\n\nSends one HTML summary email per manager, listing all relevant new applicants for the day.\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "67d27489-333a-4e73-8d9c-1af780d66304",
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "fetch_applicant_emails",
"type": "main",
"index": 0
}
]
]
},
"Assign Manager Emails": {
"main": [
[
{
"node": "Group & Build HTML Tables",
"type": "main",
"index": 0
}
]
]
},
"fetch_applicant_emails": {
"main": [
[
{
"node": "Extract Applicant Details",
"type": "main",
"index": 0
},
{
"node": "readAll_applicant_emails",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Extract Applicant Details",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Extract Applicant Details": {
"main": [
[
{
"node": "Assign Manager Emails",
"type": "main",
"index": 0
}
]
]
},
"Group & Build HTML Tables": {
"main": [
[
{
"node": "Send Digest to Managers",
"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.
gmailOAuth2googlePalmApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
Streamline your recruitment process with a daily digest that compiles applicant emails into a concise, AI-generated summary delivered straight to your inbox, saving hours of manual sorting and review. This workflow suits hiring managers or small teams handling high volumes of job applications via Gmail, automating the extraction of key details like names, experiences, and contact info using Google Gemini's chat model. The core step involves chaining the LLM to parse and structure data from fetched emails, followed by grouping into HTML tables for easy scanning.
Use this workflow when you receive 10-50 applicant emails daily and need a quick overview without sifting through inboxes, especially for roles with consistent application formats. Avoid it for very high-volume recruitment requiring advanced filtering or when emails lack standardised structures, as the AI extraction may falter. Common variations include customising the cron schedule for weekly digests or adding Slack notifications instead of email delivery for faster team alerts.
About this workflow
Daily-Applicant-Digest. Uses lmChatGoogleGemini, gmail, chainLlm. Scheduled trigger; 10 nodes.
Source: https://github.com/weblineindia/n8n-Daily-applicant-digest-by-role-with-Gemini-AI-extraction-for-hiring-managers/blob/main/main.json — 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.
kisisel asistan. Uses toolWorkflow, toolHttpRequest, toolCalculator, toolThink. Scheduled trigger; 43 nodes.
Categories Content Creation AI Automation Publishing Social Media
Automatically identifies overdue sales leads and generates personalized follow-up emails using AI. Runs every weekday Reads leads from Google Sheets Filters leads with no contact for 5+ days Downloads
Fetch user-specific research papers from arXiv on a daily schedule, process and structure the data, and create or update entries in a Notion database, with support for data delivery Paper Topic: singl
This workflow automatically analyzes competitor websites, which inseted in description field and generates a clean, structured AI‑powered Battle Card for every new Zoho CRM deal. It reads the competit