This workflow corresponds to n8n.io template #5727 — we link there as the canonical source.
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 →
{
"meta": {
"templateId": "5727"
},
"name": "Categorize Gmail emails using GPT-4o-mini with Multi-Label Analysis",
"tags": [],
"nodes": [
{
"id": "gmail-trigger",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
460,
420
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1
},
{
"id": "get-gmail-labels",
"name": "Get Gmail Labels",
"type": "n8n-nodes-base.gmail",
"position": [
680,
420
],
"parameters": {
"resource": "label",
"returnAll": true
},
"typeVersion": 2.1
},
{
"id": "get-email-content",
"name": "Get Email Content",
"type": "n8n-nodes-base.gmail",
"position": [
880,
420
],
"parameters": {
"simple": false,
"options": {},
"messageId": "={{ $('Gmail Trigger').first().json.id }}",
"operation": "get"
},
"typeVersion": 2.1
},
{
"id": "prepare-prompt",
"name": "Prepare Prompt",
"type": "n8n-nodes-base.code",
"position": [
1100,
420
],
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re l'email et les labels\nconst emailData = $('Get Email Content').first().json;\nconst allLabels = $('Get Gmail Labels').all().map(item => item.json);\n\n// Filtre les labels syst\u00e8me\nconst systemLabels = ['INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'IMPORTANT', 'STARRED'];\nconst userLabels = allLabels.filter(label => \n !systemLabels.includes(label.name.toUpperCase())\n);\n\n// Cr\u00e9er la liste des labels format\u00e9e\nconst labelsList = userLabels.map(label => `- ${label.name}`).join('\\n');\n\n// Pr\u00e9parer le contenu de l'email\nconst emailContent = emailData.text || emailData.body || emailData.snippet || 'Pas de contenu disponible';\n\n// Cr\u00e9er le prompt final MULTI-LABELS\nconst prompt = `Analyse cet email et choisis les MEILLEURS LABELS (1 \u00e0 3 maximum) parmi ceux disponibles :\n\nEMAIL \u00c0 ANALYSER :\nDe: ${emailData.from}\nObjet: ${emailData.subject}\nContenu: ${emailContent}\n\nLABELS DISPONIBLES :\n${labelsList}\n\nINSTRUCTIONS :\n1. Analyse le contenu et le contexte de l'email\n2. Choisis entre 1 et 3 labels les PLUS APPROPRI\u00c9S parmi la liste\n3. Privil\u00e9gie la qualit\u00e9 \u00e0 la quantit\u00e9 - si un seul label convient parfaitement, n'en mets qu'un\n4. \u00c9vite les labels syst\u00e8me comme INBOX, SENT, SPAM, etc.\n5. Classe les labels par ordre de pertinence (plus pertinent en premier)\n\nR\u00e9ponds en JSON avec la liste des labels choisis : {\"labels\": [\"label1\", \"label2\", \"label3\"]}`;\n\nconsole.log('\ud83d\udcdd Prompt multi-labels cr\u00e9\u00e9:', prompt);\nconsole.log('\ud83c\udff7\ufe0f Labels disponibles:', userLabels.map(l => l.name));\n\nreturn [{\n json: {\n prompt: prompt,\n email: emailData,\n labels: allLabels,\n userLabels: userLabels\n }\n}];"
},
"typeVersion": 2
},
{
"id": "openai-chat-model",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1340,
600
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "llm-chain",
"name": "GPT\u20114o\u2011mini Label Analyzer",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1340,
420
],
"parameters": {
"text": "={{ $json.prompt }}",
"promptType": "define"
},
"typeVersion": 1.5
},
{
"id": "find-labels-id",
"name": "Find Multi-Labels ID",
"type": "n8n-nodes-base.code",
"position": [
1700,
420
],
"parameters": {
"jsCode": "// Get LLM Chain response\nconst llmResponse = items[0].json;\n\n// Get labels from Prepare Prompt node\nconst preparePromptData = $('Prepare Prompt').first().json;\nconst allLabels = preparePromptData.labels || [];\n\nconsole.log('\ud83e\udd16 LLM Response Raw:', llmResponse);\nconsole.log('\ud83c\udff7\ufe0f All Labels:', allLabels.map(l => l.name));\n\n// Extract response content (different possible properties)\nlet responseText = '';\nif (llmResponse.text) {\n responseText = llmResponse.text;\n} else if (llmResponse.response) {\n responseText = llmResponse.response;\n} else if (llmResponse.output) {\n responseText = llmResponse.output;\n} else if (typeof llmResponse === 'string') {\n responseText = llmResponse;\n} else {\n responseText = JSON.stringify(llmResponse);\n}\n\nconsole.log('\ud83d\udcdd Response Text:', responseText);\n\n// Extract label names from response\nlet labelNames = [];\n\n// Try to parse JSON first\ntry {\n const parsed = JSON.parse(responseText);\n if (parsed.labels && Array.isArray(parsed.labels)) {\n labelNames = parsed.labels.map(label => label.trim()).filter(label => label.length > 0);\n } else if (parsed.label) {\n // Fallback for old single label format\n labelNames = [parsed.label.trim()];\n }\n} catch (e) {\n // If not JSON, look for pattern {\"labels\": [...]}\n const jsonMatch = responseText.match(/\\{[^}]*\"labels\"[^}]*\\[([^\\]]+)\\][^}]*\\}/);\n if (jsonMatch) {\n const labelsString = jsonMatch[1];\n labelNames = labelsString.split(',').map(label => \n label.replace(/[\"']/g, '').trim()\n ).filter(label => label.length > 0);\n } else {\n // Fallback: look for old single label format\n const singleMatch = responseText.match(/\\{[^}]*\"label\"[^}]*\"([^\"]+)\"[^}]*\\}/);\n if (singleMatch) {\n labelNames = [singleMatch[1].trim()];\n } else {\n // Last fallback: take first mentioned labels\n for (const label of allLabels.slice(0, 3)) {\n if (responseText.toLowerCase().includes(label.name.toLowerCase())) {\n labelNames.push(label.name);\n }\n }\n }\n }\n}\n\n// Limit to 3 labels max\nlabelNames = labelNames.slice(0, 3);\n\nconsole.log('\ud83c\udfaf Extracted Label Names:', labelNames);\n\n// Find corresponding labels\nlet selectedLabels = [];\n\nfor (const labelName of labelNames) {\n // Exact match\n let foundLabel = allLabels.find(label => \n label.name.toLowerCase() === labelName.toLowerCase()\n );\n \n // Fallback: partial match\n if (!foundLabel) {\n foundLabel = allLabels.find(label => \n label.name.toLowerCase().includes(labelName.toLowerCase()) ||\n labelName.toLowerCase().includes(label.name.toLowerCase())\n );\n }\n \n if (foundLabel && !selectedLabels.find(l => l.id === foundLabel.id)) {\n // Avoid duplicates\n selectedLabels.push(foundLabel);\n }\n}\n\n// If no label found, fallback to first non-system label\nif (selectedLabels.length === 0) {\n const systemLabels = ['INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'IMPORTANT', 'STARRED'];\n const fallbackLabel = allLabels.find(label => \n !systemLabels.includes(label.name.toUpperCase())\n ) || allLabels[0];\n \n if (fallbackLabel) {\n selectedLabels = [fallbackLabel];\n }\n}\n\nif (selectedLabels.length === 0) {\n console.error('\u274c No labels found!');\n return [{ json: { error: 'No labels found' } }];\n}\n\nconsole.log('\u2705 Final Selected Labels:', selectedLabels.map(l => `${l.name} (${l.id})`));\n\n// Return label IDs for application\nconst labelIds = selectedLabels.map(label => label.id);\n\nreturn [{\n json: {\n labelIds: labelIds,\n labels: selectedLabels,\n labelNames: selectedLabels.map(l => l.name),\n llmResponse: responseText,\n extractedLabelNames: labelNames,\n count: selectedLabels.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "apply-multi-labels",
"name": "Apply Multi-Labels",
"type": "n8n-nodes-base.gmail",
"position": [
2060,
420
],
"parameters": {
"labelIds": "={{ $json.labelIds }}",
"messageId": "={{ $('Gmail Trigger').first().json.id }}",
"operation": "addLabels"
},
"typeVersion": 2.1
},
{
"id": "main-sticky-note",
"name": "\ud83d\udccb USER GUIDE",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
720
],
"parameters": {
"width": 650,
"height": 600,
"content": "\ud83c\udfaf GMAIL ASSISTANT MULTI-LABELS (1-3 max)\n===========================================\n\n\ud83d\ude80 HOW TO USE THIS WORKFLOW:\n\n1\ufe0f\u20e3 ACTIVATION\n \u2022 Click \"Activate\" button in top right\n \u2022 Verify Gmail and OpenAI credentials are configured\n \u2022 Workflow automatically monitors new emails\n\n2\ufe0f\u20e3 AUTOMATIC OPERATION\n \u2022 As soon as new email arrives\n \u2022 AI analyzes content (sender, subject, body)\n \u2022 Smart selection of 1 to 3 relevant labels\n \u2022 Automatic label application\n\n3\ufe0f\u20e3 ADVANTAGES\n \u2022 Only 1 ChatGPT call per email (cost-effective)\n \u2022 Up to 3 labels per email (more precise)\n \u2022 Automatic adaptation to existing labels\n \u2022 No configuration required\n\n\ud83d\udca1 BASED ON THE WORKING WORKFLOW\n Stable and proven version adapted for multi-labels\n\n\ud83c\udf8a READY TO USE! Activate and let the magic happen!"
},
"typeVersion": 1
},
{
"id": "trigger-sticky",
"name": "\ud83d\udce7 TRIGGER",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
240
],
"parameters": {
"color": 2,
"width": 660,
"height": 480,
"content": "\ud83d\udce7 EMAIL MONITORING\n\u2022 Checks every minute\n\u2022 Triggers on new emails\n\u2022 Retrieves message ID"
},
"typeVersion": 1
},
{
"id": "ai-sticky",
"name": "\ud83e\udd16 AI INTELLIGENCE",
"type": "n8n-nodes-base.stickyNote",
"position": [
1240,
240
],
"parameters": {
"color": 3,
"width": 430,
"height": 480,
"content": "\ud83e\udd16 AI MULTI-LABEL ANALYSIS\n\u2022 Optimized prompt for 1-3 labels\n\u2022 Cost-effective GPT-4o-mini\n\u2022 Structured JSON response\n\u2022 Smart contextual analysis"
},
"typeVersion": 1
},
{
"id": "processing-sticky",
"name": "\u2699\ufe0f PROCESSING",
"type": "n8n-nodes-base.stickyNote",
"position": [
1040,
240
],
"parameters": {
"color": 4,
"width": 200,
"height": 480,
"content": "\u2699\ufe0f DATA PREPARATION\n\u2022 Filters system labels\n\u2022 Creates multi-label prompt\n\u2022 Optimizes for AI\n\u2022 Provides full context"
},
"typeVersion": 1
},
{
"id": "parsing-sticky",
"name": "\ud83d\udd27 PARSING",
"type": "n8n-nodes-base.stickyNote",
"position": [
1660,
240
],
"parameters": {
"color": 5,
"width": 260,
"height": 480,
"content": "\ud83d\udd27 SMART PARSING\n\u2022 Parses JSON arrays [\"label1\", \"label2\"]\n\u2022 Fallback to single label format\n\u2022 Exact + partial matching\n\u2022 Avoids duplicates\n\u2022 Limits to 3 labels max"
},
"typeVersion": 1
},
{
"id": "final-sticky",
"name": "\u2705 APPLICATION",
"type": "n8n-nodes-base.stickyNote",
"position": [
1920,
240
],
"parameters": {
"color": 6,
"width": 360,
"height": 480,
"content": "\u2705 FINAL APPLICATION\n\u2022 Applies 1-3 selected labels\n\u2022 Uses labelIds array\n\u2022 Direct modification in Gmail\n\u2022 Automatic confirmation"
},
"typeVersion": 1
},
{
"id": "tips-sticky",
"name": "\ud83d\udca1 TIPS",
"type": "n8n-nodes-base.stickyNote",
"position": [
1040,
880
],
"parameters": {
"color": 4,
"width": 180,
"height": 200,
"content": "\ud83d\udca1 USAGE TIPS\n\u2022 Test with one email first\n\u2022 Check logs for debugging\n\u2022 Adjust prompt if necessary\n\u2022 Create specific labels before use"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "",
"connections": {
"LLM Chain": {
"main": [
[
{
"node": "Find Multi-Labels ID",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Get Gmail Labels",
"type": "main",
"index": 0
}
]
]
},
"Prepare Prompt": {
"main": [
[
{
"node": "LLM Chain",
"type": "main",
"index": 0
},
{
"node": "GPT\u20114o\u2011mini Label Analyzer",
"type": "main",
"index": 0
}
]
]
},
"Get Gmail Labels": {
"main": [
[
{
"node": "Get Email Content",
"type": "main",
"index": 0
}
]
]
},
"Get Email Content": {
"main": [
[
{
"node": "Prepare Prompt",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "LLM Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Find Multi-Labels ID": {
"main": [
[
{
"node": "Apply Multi-Labels",
"type": "main",
"index": 0
}
]
]
},
"GPT\u20114o\u2011mini Label Analyzer": {
"main": [
[
{
"node": "Find Multi-Labels ID",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
The "Automatically Categorize Gmail Emails with GPT-4o-mini Multi-Label Analysis" template is designed specifically for professionals, business owners, entrepreneurs, and anyone struggling to manage a high volume of daily emails. It solves common inbox problems such as email…
Source: https://n8n.io/workflows/5727/ — 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.
Email-Automation-Template. Uses lmChatAnthropic, microsoftOutlookTrigger, gmail, chainLlm. Event-driven trigger; 38 nodes.
Use cases Automatically detect and classify sponsorship inquiries from your Gmail inbox Extract company name, contact, budget, campaign type, and channel from emails using AI Log every deal to a Notio
This workflow automates the full lifecycle of a vehicle insurance claim — from an incoming Gmail email to a signed, watermarked PDF decision letter delivered back to the claimant.
A sophisticated n8n workflow that transforms your email management with AI-powered classification, automatic responses, and intelligent organization.
AI Agents Vs AI Workflow. Uses lmChatOpenAi, gmailTrigger, gmail, gmailTool. Event-driven trigger; 30 nodes.