This workflow corresponds to n8n.io template #15805 — we link there as the canonical source.
This workflow follows the Agent → Datatable 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": "a15AtkrrX12XRim4",
"meta": {
"templateId": "3502",
"templateCredsSetupCompleted": true
},
"name": "Smart Gmail cleaner with AI validator & Telegram alerts",
"tags": [],
"nodes": [
{
"id": "43bcc829-a428-4f29-9d4d-2570ad162e09",
"name": "AI Check Email",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"position": [
-176,
96
],
"parameters": {
"text": "=Email to review:\n{{ JSON.stringify($json) }}\n\n",
"options": {
"systemMessage": "=You are an AI inbox triage assistant.\n\nYour job is to safely analyze incoming emails and decide which labels should be applied. You must avoid hiding or mishandling important emails. Think like an executive assistant who is still learning the user's preferences.\n\nThe user-defined confidence threshold is: {{ $json.confidenceThreshold }}\n\nYou must return only valid JSON matching the required output schema.\n\nCore workflow:\n1. Analyze the email.\n2. Choose the best labels to apply.\n3. Estimate confidence from 0 to 1.\n4. If confidence is greater than or equal to {{ $json.confidenceThreshold }}, apply the appropriate labels.\n5. If confidence is below {{ $json.confidenceThreshold }}, use the available tool to search for previous emails or decisions from the same sender.\n6. If historical data clearly supports the same handling, increase confidence to at least {{ $json.confidenceThreshold }} (only if the given data's isPending status is false) and use the recommended action or apply the historically supported labels.\n7. If no useful historical data exists and confidence remains below {{ $json.confidenceThreshold }}, apply AI/Needs Review, ask the user how to handle similar emails moving forward, and provide a recommendation in ruleSuggestion.\n\nLabel instructions:\n\nAI/Needs Review:\nUse when the email is uncertain, confidence is below {{ $json.confidenceThreshold }}, the sender is unfamiliar, the email could be important, or the assistant needs the user to teach a preference.\n\nAI/Archive:\nUse when the email appears safe to remove from the inbox using a Gmail rule, such as low-value marketing, routine newsletters, repeated notifications, or non-actionable updates. Do not use if the email may involve billing, security, legal, appointments, clients, customers, staff, deadlines, account access, or money.\n\nAI/Important:\nUse for emails that likely need the user's attention. This includes client/customer messages, business-critical updates, deadlines, legal/financial/account issues, staff-related items, security alerts, scheduling, or anything that could cause harm if missed.\n\nAI/Reply Needed:\nUse when the email appears to require or invite a response from the user.\n\nAI/Billing or Invoice:\nUse for invoices, bills, receipts requiring review, payment issues, failed payments, subscriptions, renewals, pricing changes, tax documents, or financial notices.\n\nAI/Security or Account:\nUse for login alerts, password changes, authentication, account changes, suspicious activity, privacy notices, access changes, or security-sensitive messages.\n\nAI/Appointment or Scheduling:\nUse for meetings, bookings, calendar updates, reminders, appointment confirmations, reschedules, cancellations, or time-sensitive scheduling messages.\n\nAI/Receipt or Order:\nUse for confirmations, orders, shipping, delivery, purchase receipts, tracking, returns, or transactional commerce emails that do not appear to require action.\n\nAI/Newsletter:\nUse for recurring editorial, educational, content-based, digest, community, or publication-style emails.\n\nAI/Marketing:\nUse for promotional emails, sales offers, discounts, product announcements, campaigns, webinars, lead nurturing, or vendor outreach. Be careful: if the email mentions billing, account changes, security, deadlines, compliance, renewals, benefits, or customer activity, add the relevant important label too and consider needsUserReview true.\n\nAI/Notification:\nUse for automated updates, system messages, app notifications, alerts, status updates, or routine informational messages.\n\nAI/Spam Like:\nUse only when the email appears unsolicited, suspicious, scammy, deceptive, irrelevant, or potentially malicious. Do not mark legitimate business, billing, security, or account emails as spam-like unless there are strong signs of abuse.\n\nAI/Low Priority:\nUse for emails that are likely safe to defer, skim later, or ignore without immediate consequence.\n\n\nDecision rules:\n- Always include AI/Needs Review when confidence is below the threshold.\n- Never invent labels outside the allowed enum.\n- Never recommend deleting emails.\n- Prefer safety over automation.\n- Always use the tool to fetch historical data to help make a more informed decision.\n- If a historical data has isPending set to true, use the information as weak reference. DO NOT increase your confidence if you based your decision on historical data where isPending is true.\n- If an email could be important, do not use AI/Archive unless historical data strongly supports that choice.\n- If historical data is used, mention that in reason.\n- If asking the user, userQuestion should be short, clear, and focused on how to handle this sender or email type in the future.\n- ruleSuggestion should be plain English and should only reference supported labels/actions."
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 1.8,
"alwaysOutputData": true
},
{
"id": "477904d8-0971-4118-b8e4-621fd7947dc1",
"name": "Unwanted Email Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-160,
624
],
"parameters": {
"autoFix": true,
"schemaType": "manual",
"inputSchema": "={\n \"type\": \"object\",\n \"required\": [\n \"emailId\",\n \"threadId\",\n \"fromEmail\",\n \"fromName\",\n \"subject\",\n \"confidence\",\n \"labelsToApply\",\n \"reason\",\n \"userQuestion\",\n \"needsUserReview\",\n \"recommendedAction\",\n \"ruleSuggestion\"\n ],\n \"properties\": {\n \"emailId\": {\n \"type\": \"string\",\n \"description\": \"The unique Gmail message ID for this email. Use the id from the email input. Do not invent this value.\"\n },\n \"threadId\": {\n \"type\": \"string\",\n \"description\": \"The Gmail thread ID for this email if available. Use an empty string if it is not available.\"\n },\n \"fromEmail\": {\n \"type\": \"string\",\n \"description\": \"The sender's email address. Extract this from the email's From field.\"\n },\n \"fromName\": {\n \"type\": \"string\",\n \"description\": \"The sender's display name if available. Use an empty string if no display name is available.\"\n },\n \"subject\": {\n \"type\": \"string\",\n \"description\": \"The subject line of the email. Use the exact subject from the email when possible.\"\n },\n \"needsUserReview\": {\n \"type\": \"boolean\",\n \"description\": \"Only set to true when confidence is below {{ $json.confidenceThreshold }}\"\n },\n \"recommendedAction\": {\n \"type\": \"string\",\n \"description\": \"The safest next action. Use label_only when the email should only receive labels. Use ask_user when the assistant is unsure, need to ask ask a question, need to review or make a new email rule. Use draft_reply only when a reply is clearly needed. Do not suggest actions outside this enum.\",\n \"enum\": [\n \"keep_in_inbox\",\n \"label_only\",\n \"draft_reply\",\n \"ask_user\",\n \"mark_spam\",\n \"ignore\"\n ]\n },\n \"confidence\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1,\n \"description\": \"A decimal from 0 to 1 showing confidence in the labels, and recommendedAction. Use lower confidence when the email could be important or context is missing.\"\n },\n \"labelsToApply\": {\n \"type\": \"array\",\n \"description\": \"These are the supported labels and represents the exact labels that should be applied to this email. Only use labels from this enum based on the user's instructions\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [{{ $('filter for AI labels').all().map(item => `\"${item.json.name}\"`) }}]\n }\n },\n \"reason\": {\n \"type\": \"string\",\n \"description\": \"A short plain-English explanation for the classification and labels. Mention the signal used, such as sender, subject, content, or risk. Keep it concise.\"\n },\n \"userQuestion\": {\n \"type\": \"string\",\n \"description\": \"When confidence is below {{ $json.confidenceThreshold }} ask a short question about what labels to apply to the given email. Do not include button labels, fake commands, or unsupported label names. Use an empty string when confidence is {{ $json.confidenceThreshold }} or higher\"\n },\n \"ruleSuggestion\": {\n \"type\": \"string\",\n \"description\": \"A plain-English suggested rule the user could approve for future emails. Only describe behavior using supported labels. Do not invent labels or actions. Use an empty string if no rule is useful.\"\n }\n }\n}"
},
"typeVersion": 1.2
},
{
"id": "2e7ef7f3-14de-42cc-a25d-6017b23aa797",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-224,
320
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5.4-mini",
"cachedResultName": "gpt-5.4-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "4e76603e-6017-474e-9b23-3469265539d6",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-160,
784
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "fce9f2d1-d25c-4b2b-aa19-9b784dd3a2a3",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-1344,
96
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "8be21462-c7ac-42ee-bb7b-4be1846c2c31",
"name": "Telegram: Error",
"type": "n8n-nodes-base.telegram",
"onError": "continueErrorOutput",
"position": [
160,
240
],
"parameters": {
"text": "=AI Error | Can't Check Email | Error: {{ JSON.stringify($json) }}",
"chatId": "<YOUR CHAT ID>",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2,
"alwaysOutputData": false
},
{
"id": "59731420-5b45-4bec-9570-e2340b2195d2",
"name": "Previous Rules and actions tool",
"type": "n8n-nodes-base.dataTableTool",
"position": [
-128,
464
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "fromEmail",
"keyValue": "={{ $json.From }}"
}
]
},
"matchType": "allConditions",
"operation": "get",
"returnAll": true,
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "uFMxAcgrHjjG9FUC",
"cachedResultUrl": "/projects/pd2MUftC6TI2VHQJ/datatables/uFMxAcgrHjjG9FUC",
"cachedResultName": "Email Rules"
},
"descriptionType": "manual",
"toolDescription": "Search for rules and actions from previous emails"
},
"typeVersion": 1.1
},
{
"id": "67a8580f-0bed-4898-9f5f-e65aa694f342",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-992,
1584
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e6a06737-24dd-447a-9ba4-bfc489a31b04",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-128,
1584
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "70a88674-dec8-4c7a-94e2-3d7165d8acde",
"name": "Telegram: Train",
"type": "n8n-nodes-base.telegram",
"position": [
128,
1600
],
"parameters": {
"chatId": "<YOUR CHAT ID>",
"message": "=Help me understand how to handle the following email.\n\n**From:** {{ $json.fromEmail }}\n**Subject:** {{ $json.subject }}\n{{ $json.snippet }}\n\n**Why am I asking about this email?**\n{{ $json.reason }}",
"options": {
"limitWaitTime": {
"values": {
"resumeUnit": "minutes",
"resumeAmount": 5
}
},
"appendAttribution": false,
"responseFormTitle": "=Email rule training for {{ $json.fromEmail }}",
"responseFormDescription": "=**Subject:** {{ $json.subject }}\n{{ $json.snippet }}"
},
"operation": "sendAndWait",
"defineForm": "json",
"jsonOutput": "=[\n {\n \"fieldLabel\": \"Do you want to add a new rule\",\n \"fieldType\": \"radio\",\n \"requiredField\": true,\n \"fieldOptions\": {\n \"values\": [\n {\n \"option\": \"Yes\"\n },\n {\n \"option\": \"No\"\n }\n ]\n }\n },\n {\n \"fieldLabel\": \"Choose all the labels that should apply to the email\",\n \"fieldType\": \"checkbox\",\n \"fieldOptions\": {\n \"values\": {{ $('get AI labels').all().map(k =>({\"option\": k.json.name})) }}\n }\n },\n {\n \"fieldLabel\": \"Anything else I should know about how to handle this email?\",\n \"fieldType\": \"textarea\",\n \"requiredField\": false\n }\n]\n ",
"responseType": "customForm"
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2,
"alwaysOutputData": false
},
{
"id": "b8729c6a-cf1f-4c3a-8cb7-a4c36600412b",
"name": "filter for AI labels",
"type": "n8n-nodes-base.filter",
"position": [
-752,
96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f732b50c-d424-45ef-97a9-460902e04707",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.name }}",
"rightValue": "AI/"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "aadf8d83-0a49-4fd0-8e56-86061d7d6fdf",
"name": "Get all labels",
"type": "n8n-nodes-base.gmail",
"position": [
-736,
1584
],
"parameters": {
"resource": "label",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "e69067dd-7b07-4819-88d8-e8656a3fa976",
"name": "Get labels",
"type": "n8n-nodes-base.gmail",
"position": [
-912,
96
],
"parameters": {
"resource": "label",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "b994b988-526b-44ed-b222-6a52c4e1ea00",
"name": "get AI labels",
"type": "n8n-nodes-base.filter",
"position": [
-560,
1584
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f732b50c-d424-45ef-97a9-460902e04707",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.name }}",
"rightValue": "AI/"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "3609cf9c-5bd6-4d3a-9895-1795f0176b4e",
"name": "Update rule",
"type": "n8n-nodes-base.dataTable",
"position": [
784,
1456
],
"parameters": {
"columns": {
"value": {
"reason": "=null",
"isPending": false,
"confidence": 1,
"userQuestion": "=null",
"labelsApplied": "={{ $json.data['Choose all the labels that should apply to the email'].join(\",\") }}",
"ruleSuggestion": "={{ $json.data['Anything else I should know about how to handle this email?'] }}",
"recommendedAction": "={{ $json.data['Anything else I should know about how to handle this email?'] }}"
},
"schema": [
{
"id": "emailId",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "emailId",
"defaultMatch": false
},
{
"id": "threadId",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "threadId",
"defaultMatch": false
},
{
"id": "fromEmail",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "fromEmail",
"defaultMatch": false
},
{
"id": "fromName",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "fromName",
"defaultMatch": false
},
{
"id": "subject",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "subject",
"defaultMatch": false
},
{
"id": "snippet",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "snippet",
"defaultMatch": false
},
{
"id": "confidence",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "confidence",
"defaultMatch": false
},
{
"id": "labelsApplied",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "labelsApplied",
"defaultMatch": false
},
{
"id": "reason",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "reason",
"defaultMatch": false
},
{
"id": "userQuestion",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "userQuestion",
"defaultMatch": false
},
{
"id": "ruleSuggestion",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "ruleSuggestion",
"defaultMatch": false
},
{
"id": "isPending",
"type": "boolean",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "isPending",
"defaultMatch": false
},
{
"id": "recommendedAction",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "recommendedAction",
"defaultMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"filters": {
"conditions": [
{
"keyValue": "={{ $('Loop Over Items').item.json.id }}"
}
]
},
"options": {},
"matchType": "allConditions",
"operation": "update",
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "uFMxAcgrHjjG9FUC",
"cachedResultUrl": "/projects/pd2MUftC6TI2VHQJ/datatables/uFMxAcgrHjjG9FUC",
"cachedResultName": "Email Rules"
}
},
"typeVersion": 1.1
},
{
"id": "1590fe74-f346-4412-832c-39a82a8a2759",
"name": "Remove label: AI, Needs Review",
"type": "n8n-nodes-base.gmail",
"position": [
1296,
1616
],
"parameters": {
"labelIds": [
"Label_6856784292070680055",
"Label_61124+1234567890"
],
"messageId": "={{ $('Loop Over Items').item.json.emailId }}",
"operation": "removeLabels"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "bbb081e4-2fe3-4825-a18d-332e0841c9a7",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-1120,
288
],
"parameters": {
"filters": {
"q": "=older:{{ Math.floor($now.toSeconds()) }} newer: {{ Math.floor($today.minus(14 , 'days').toSeconds()) }} -label:AI"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "2e267416-f0e0-45e3-9197-4251b9f2f769",
"name": "Add rule for review",
"type": "n8n-nodes-base.dataTable",
"position": [
608,
64
],
"parameters": {
"columns": {
"value": {
"reason": "={{ $json.reason }}",
"emailId": "={{ $json.emailId }}",
"snippet": "={{ $json.snippet }}",
"subject": "={{ $json.subject }}",
"fromName": "={{ $json.fromName }}",
"threadId": "={{ $json.threadId }}",
"fromEmail": "={{ $json.fromEmail }}",
"isPending": true,
"confidence": "={{ $json.confidence }}",
"userQuestion": "={{ $json.userQuestion }}",
"labelsApplied": "={{ $json.labelsToApply.join(\", \") }}",
"ruleSuggestion": "={{ $json.ruleSuggestion }}"
},
"schema": [
{
"id": "emailId",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "emailId",
"defaultMatch": false
},
{
"id": "threadId",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "threadId",
"defaultMatch": false
},
{
"id": "fromEmail",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "fromEmail",
"defaultMatch": false
},
{
"id": "fromName",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "fromName",
"defaultMatch": false
},
{
"id": "subject",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "subject",
"defaultMatch": false
},
{
"id": "snippet",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "snippet",
"defaultMatch": false
},
{
"id": "importanceLevel",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "importanceLevel",
"defaultMatch": false
},
{
"id": "confidence",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "confidence",
"defaultMatch": false
},
{
"id": "riskLevel",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "riskLevel",
"defaultMatch": false
},
{
"id": "labelsApplied",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "labelsApplied",
"defaultMatch": false
},
{
"id": "reason",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "reason",
"defaultMatch": false
},
{
"id": "userQuestion",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "userQuestion",
"defaultMatch": false
},
{
"id": "ruleSuggestion",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "ruleSuggestion",
"defaultMatch": false
},
{
"id": "isPending",
"type": "boolean",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "isPending",
"defaultMatch": false
},
{
"id": "recommendedAction",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "recommendedAction",
"defaultMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "uFMxAcgrHjjG9FUC",
"cachedResultUrl": "/projects/pd2MUftC6TI2VHQJ/datatables/uFMxAcgrHjjG9FUC",
"cachedResultName": "Email Rules"
}
},
"typeVersion": 1.1
},
{
"id": "c7f8fc44-77f5-473f-aba1-32f8db252997",
"name": "add label & email metadata to ai output",
"type": "n8n-nodes-base.code",
"position": [
160,
80
],
"parameters": {
"jsCode": "return $input.all().map((aiResponse, idx) => {\n const {output} = aiResponse.json\n const {labelsToApply} = output\n const {From, snippet, labels} = $('Add label map ').all().at(idx)?.json\n const labelIds = labelsToApply.map(name => labels[name])\n .filter(Boolean)\n .join(\",\")\n \n return {\n ...output,\n fromEmail: From,\n snippet,\n labelIds,\n }\n});"
},
"typeVersion": 2
},
{
"id": "a4ffec02-bbdd-4faf-8639-9fe4975cd0f4",
"name": "Get all pending rules",
"type": "n8n-nodes-base.dataTable",
"position": [
-352,
1584
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "isPending",
"condition": "isTrue"
}
]
},
"matchType": "allConditions",
"operation": "get",
"returnAll": true,
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "uFMxAcgrHjjG9FUC",
"cachedResultUrl": "/projects/pd2MUftC6TI2VHQJ/datatables/uFMxAcgrHjjG9FUC",
"cachedResultName": "Email Rules"
}
},
"executeOnce": true,
"typeVersion": 1.1,
"alwaysOutputData": false
},
{
"id": "24998b66-875d-4dbb-b51e-0820b37f0b3d",
"name": "Delete rule",
"type": "n8n-nodes-base.dataTable",
"position": [
784,
1616
],
"parameters": {
"filters": {
"conditions": [
{
"keyValue": "={{ $('Loop Over Items').item.json.id }}"
}
]
},
"options": {},
"matchType": "allConditions",
"operation": "deleteRows",
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "uFMxAcgrHjjG9FUC",
"cachedResultUrl": "/projects/pd2MUftC6TI2VHQJ/datatables/uFMxAcgrHjjG9FUC",
"cachedResultName": "Email Rules"
}
},
"typeVersion": 1.1
},
{
"id": "72bdac4a-d6a2-499c-af98-6d7d81000dfe",
"name": "Message: Deleted rule",
"type": "n8n-nodes-base.telegram",
"position": [
1040,
1616
],
"parameters": {
"text": "=Deleted the rule. I will remove any labels on the email from {{ $('Loop Over Items').item.json.fromEmail }}",
"chatId": "<YOUR CHAT ID>",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "7ee761d6-2c63-4cab-9388-66f0d3fdbd87",
"name": "Message: Added rule",
"type": "n8n-nodes-base.telegram",
"position": [
1040,
1456
],
"parameters": {
"text": "=I added a new rule for {{ $('Loop Over Items').item.json.fromEmail }}.\n\nNew Rule:\n{{ $('Switch').item.json.data['Anything else I should know about how to handle this email?'] || \"AI will use best judgment and apply the recommended labels\" }}\nRecommended Labels:\n```\n{{ $('Switch').item.json.data['Choose all the labels that should apply to the email'] }}\n```",
"chatId": "<YOUR CHAT ID>",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "223f8b9a-b551-4761-927e-936908829306",
"name": "Add AI recommended labels",
"type": "n8n-nodes-base.gmail",
"position": [
800,
96
],
"parameters": {
"labelIds": "={{ $('If low confidence...').item.json.labelIds.split(\",\").map(l => l.trim())}}",
"messageId": "={{ $('If low confidence...').item.json.emailId }}",
"operation": "addLabels"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "05803d8e-bfc7-4a88-b910-50266aed91ee",
"name": "Add fixed labels",
"type": "n8n-nodes-base.gmail",
"position": [
992,
96
],
"parameters": {
"labelIds": [
"Label_61124+1234567890"
],
"messageId": "={{ $json.id }}",
"operation": "addLabels"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "7b0c05ae-ac01-43f4-9b47-ef0553b61a93",
"name": "Gmail backfill",
"type": "n8n-nodes-base.gmail",
"position": [
-1120,
96
],
"parameters": {
"filters": {
"q": "=older:{{ Math.floor($now.toSeconds()) }} newer: {{ Math.floor($today.minus(14 , 'days').toSeconds()) }} -label:AI"
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2,
"alwaysOutputData": false
},
{
"id": "29401c6c-aae7-40ab-a80f-aa7e16ddfac5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
-544
],
"parameters": {
"width": 384,
"height": 992,
"content": "## Trigger\nThis section is responsible for fetching emails from Gmail.\n\nThe workflow supports two ways to process emails:\n\n### Gmail Trigger\nThe Gmail Trigger continuously watches the inbox for new emails every minute.\n\nQuery used: `-label:AI`\n\nThis prevents emails that were already processed by the workflow from being reprocessed again.\n\n### Gmail Backfill\nThe backfill node is used to process historical emails.\n\nThis is useful when:\n- Running the workflow for the first time\n- Reprocessing old emails\n- Testing AI classifications on existing inbox data\n\n\nThe backfill query searches for emails within the last 14 days that do not already have the AI label applied."
},
"typeVersion": 1
},
{
"id": "72fffbd2-6d6d-41dc-92fd-a6ed8b79c04f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-944,
-1008
],
"parameters": {
"width": 704,
"height": 1456,
"content": "## Create labels & set thresholds\nThis section dynamically gathers all Gmail labels that begin with: `AI/`\n\nExamples:\n```\nAI/Important\nAI/Needs Review\nAI/Marketing\nAI/Newsletter\nAI/Archive\n```\n\n### Important\nYou labels names do not need to match my label names however they do need a specific structure.\n1. In gmail, create a label with a unique parent name. For my system i called that parent label `AI`\n2. Create sub-labels under the new parent label. In my system i created `Important`, ` Marketing` etc.. When Gmail creates the pretty name it will automatically add the forward-slash so it will look like `AI/Important` `AI/Marketing`.\n\n#### Notice\nAt the end of the flow there is a node that applies a fixed label of `AI` to all of the email. This is the parent label. This will make it easy for you to find All of the emails that have been touched by AI but is vital for this system as it will filter out these emails to prevent the same emails from being processed over and over again. **If you adopt a different naming schema, ensure that you update the nodes where it specifically looks for `AI` parent label**\n\n### Why Dynamic Label Discovery Matters\nFor the most part I did my best to ensure the labels remain dynamic:\n- Users can customize their own labels\n- Labels stay synchronized with Gmail\n- The AI can only select labels that actually exist\n\nThis reduces hallucinations and prevents invalid label creation.\n\n### Confidence Threshold\nThe workflow adds a configurable confidence threshold to every email.\n\nExample:\n`item.json.confidenceThreshold = .9;`\n\nUpdate this value to meet your needs!!\n\nThe threshold determines whether:\n- The AI can confidently auto-label the email\n- The email should instead be reviewed by the user\n\n\n| Threshold | Behavior |\n| --------- | ------------------------------- |\n| 0.95 | Very conservative |\n| 0.90 | Recommended |\n| 0.80 | More aggressive automation |\n| 0.70 | High automation but higher risk |\n"
},
"typeVersion": 1
},
{
"id": "8fa03eb4-7c56-493f-8a6d-dc66ebf053c1",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-1008
],
"parameters": {
"width": 320,
"height": 1456,
"content": "## Agentic business logic\nThis is the core AI decision-making section of the workflow.\n\nThe AI receives:\n- The raw email\n- Existing AI labels\n- Confidence threshold\n- Historical rule data from previous emails\n\n\nThe AI then decides:\n- Which labels should be applied\n- Whether the email is safe to automate\n- Whether the user should review the email\n- Whether historical behavior supports the decision\n\n### Historical Learning\nBefore asking the user for help, the AI checks previous decisions from the same sender.\n\nIf similar emails were previously reviewed and approved:\n- Confidence may increase\n- Existing labels may be reused\n- Automation becomes smarter over time\n\n\nPending or unreviewed rules are treated as weak references only.\n\n### UPDATE REQUIRED\nGo through both the system prompt and the structured data to make sure the rules and logic fits your use case! \n\nYou'll notice that the system prompt defines each label. This increases accuracy and stability. There are placeholders for the confidence level as well which helps the AI make better decisions. \n\n#### Improvement\nFeel free to update the model to one of your choice. Additionally You could store your system prompt in a separate accessible area where you can define the behavior and dynamically pass it back to the AI.\n"
},
"typeVersion": 1
},
{
"id": "ce9e305f-ac32-4ce6-a613-c23bfe8f6726",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
576,
-1008
],
"parameters": {
"width": 624,
"height": 1456,
"content": "## Apply labels and create rules\nThis section applies Gmail labels and stores review data for future learning.\n\n### AI Label Application\nThe workflow converts AI label names into Gmail label IDs.\n\nExample:\n\n```javascript\n{\n \"AI/Important\": \"Label_123\",\n \"AI/Newsletter\": \"Label_456\"\n}\n```\nThis allows the AI to work with human-readable labels while Gmail receives the correct internal label IDs.\n\n### Fixed Labels\n\nEvery processed email also receives: `AI`\nYou can apply a different label but make sure you update the search property in the trigger section so it excludes that label from the search.\n\nThis prevents future reprocessing.\n\n### Review Queue\nIf an email requires review:\n- The email data is stored in the Email Rules table\n- The record is marked as isPending = true\n\n\nStored data includes:\n- Sender\n- Subject\n- Labels\n- Confidence\n- AI reasoning\n- Rule suggestions\n- User questions\n\n\nThis creates a lightweight memory system for future AI decisions."
},
"typeVersion": 1
},
{
"id": "eb87f56f-a664-4c12-aff8-b9c8597ae384",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
144,
-1008
],
"parameters": {
"width": 400,
"height": 1456,
"content": "## Determine next steps\nAfter the AI classifies an email, the workflow determines what should happen next.\n\n\n### High Confidence Path\n\nIf the AI confidence is greater than or equal to the configured threshold:\n- AI labels are immediately applied\n- The email receives the fixed `AI` label\n- The workflow finishes processing\n\n\n### Low Confidence Path\nIf confidence is below the threshold:\n- The email receives `AI/Needs Review`\n- The email is stored in the review queue\n- The email remains in the inbox\n- The user can later train the system\n\nThis allows the AI to safely learn over time instead of making risky assumptions.\n\n"
},
"typeVersion": 1
},
{
"id": "e4e1e781-f29f-48e4-8f46-f5af2e18a2e7",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1072,
736
],
"parameters": {
"width": 848,
"height": 1072,
"content": "## Gather all pending rules\nThis workflow can be manually executed to review pending email decisions.\n\nThe workflow retrieves all records where: `isPending = true`\n\nThese represent emails the AI was not confident enough to fully automate.\n\nFor each pending rule:\n\n- The email is shown to the user\n- The AI explains why it was uncertain\n- The user selects labels\n- The workflow stores the decision\n- Future emails from that sender become easier to classify"
},
"typeVersion": 1
},
{
"id": "a41c7c94-01d0-4d0a-a942-bd02d15f3b6e",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
32,
736
],
"parameters": {
"width": 612,
"height": 1072,
"content": "## Telegram training \n\nThis section creates the human-in-the-loop training experience.\n\nTelegram is used as a lightweight review interface for training the AI.\n\n\n### User Review Form\n\nThe user receives:\n- Sender information\n- Subject line\n- Email snippet\n- AI reasoning\n\nThe user can then:\n- Approve labels\n- Reject the rule\n- Add additional instructions\n- Teach the AI how future emails should behave\n\nFeel free to replace this with another messaging platform of your choice.\n\n### Training Philosophy\n\nThe goal is to teach the AI similarly to how someone would train:\n- An executive assistant\n- A receptionist\n- An operations coordinator\n\nOver time:\n- Interruptions decrease\n- Confidence improves\n- Automation becomes more personalized"
},
"typeVersion": 1
},
{
"id": "4036dfa3-3819-442b-8e92-09a34b735ccb",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
768,
736
],
"parameters": {
"width": 640,
"height": 1072,
"content": "## Update or Delete Rule\nAfter the user reviews an email, the workflow either:\n\n- Creates/updates a rule\nOR\n- Deletes the pending review item\n\n---\n\n### Create Rule Path\n\nIf the user approves the rule:\n- Labels are stored\n- Confidence is increased\n- `isPending` becomes `false`\n- Future emails from the sender become easier to automate\n\nThe workflow also removes:\n- `AI/Needs Review`\n- Temporary review labels\n\n---\n\n### Delete Rule Path\n\nIf the user rejects the rule:\n- The pending record is deleted\n- Review labels are removed\n- The system forgets the attempted classification\n\nThis prevents low-quality rules from polluting future AI decisions.\n"
},
"typeVersion": 1
},
{
"id": "8b6b7317-d5f4-4848-9d3f-1728f5a59cc5",
"name": "If low confidence...",
"type": "n8n-nodes-base.if",
"position": [
384,
80
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ee4bc4e7-60e3-4b1e-b567-0eb972b1ef7c",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.confidence }}",
"rightValue": "={{ $('set confidence threshold').all().find(item => item.json.id === $json.emailId)?.json.confidenceThreshold }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "4a24a9bf-d25a-4ea6-92bc-9dded8c18949",
"name": "Add label map ",
"type": "n8n-nodes-base.code",
"position": [
-576,
96
],
"parameters": {
"jsCode": "const labels = {}\n\n/**\n * Create the label map. It will look like:\n * {\"Label Pretty Name\": \"unique_label_id\"}\n */\nfor (const item of $input.all()) {\n labels[item.json.name] = item.json.id\n}\n\n\n// Grab all the emails based on how this flow was triggered \nconst emails = $('Gmail Trigger').isExecuted\n ? $('Gmail Trigger').all()\n : $('Gmail backfill').all();\n\n// Add the label map and the confidence threshold\nreturn emails.map(email => ({\n ...email.json, \n labels,\n }))\n"
},
"typeVersion": 2
},
{
"id": "8ab83fa3-4f11-454e-9a82-247838ee9623",
"name": "set confidence threshold",
"type": "n8n-nodes-base.code",
"position": [
-400,
96
],
"parameters": {
"jsCode": "// Loop over input items (email) and add a new field called 'confidenceThreshold' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.confidenceThreshold = .9;\n}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "192ab2d4-c00b-459d-b867-9fadb26ca79b",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
400,
1600
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Create Rule",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "85205258-7557-4cc8-abc1-51daa521ca04",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.data['Do you want to add a new rule'] === \"Yes\" && $json.data['Choose all the labels that should apply to the email'].length > 0}}",
"rightValue": "Yes"
}
]
},
"renameOutput": true
},
{
"outputKey": "Delete Rule",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d57a4a55-b455-4081-8458-8c420ac3155f",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.data['Do you want to add a new rule'] }}",
"rightValue": "No"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "835cd7f9-87cd-4535-9ca7-3f23ccf1598c",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1792,
96
],
"parameters": {
"width": 384,
"height": 208,
"content": "## Email Label Automation \ud83d\udc49\ud83c\udffe\nThis flow will apply labels to your emails using AI. You can define what labels you want to use and how confident the AI must be to apply it. \n\nIf the AI isnt confident it will create a new rule for you to review. It will use the rule in the future to teach itself and grow smarter!"
},
"typeVersion": 1
},
{
"id": "6733c302-71ac-4f0a-ad19-5b1a98c92a5e",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1488,
1552
],
"parameters": {
"width": 400,
"height": 272,
"content": "## Email Rule Trainer Automation \ud83d\udc49\ud83c\udffe\nThis flow searches for all the pending rules you need to review. It creates a Telegram form where you can choose which labels the AI should use and provide clarity on when to apply the labels.\n\n**Once you create the new rules you should run the backfill so it can update the older emails that it was unsure about**"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "797b278a-c1c5-47c3-804e-34b99e6727da",
"connections": {
"Switch": {
"main": [
[
{
"node": "Update rule",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete rule",
"type": "main",
"index": 0
}
]
]
},
"Get labels": {
"main": [
[
{
"node": "filter for AI labels",
"type": "main",
"index": 0
}
]
]
},
"Delete rule": {
"main": [
[
{
"node": "Message: Deleted rule",
"type": "main",
"index": 0
}
]
]
},
"Update rule": {
"main": [
[
{
"node": "Message: Added rule",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Get labels",
"type": "main",
"index": 0
}
]
]
},
"get AI labels": {
"main": [
[
{
"node": "Get all pending rules",
"type": "main",
"index": 0
}
]
]
},
"AI Check Email": {
"main": [
[
{
"node": "add label & email metadata to ai output",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Error",
"type": "main",
"index": 0
}
]
]
},
"Add label map ": {
"main": [
[
{
"node": "set confidence threshold",
"type": "main",
"index": 0
}
]
]
},
"Get all labels": {
"main": [
[
{
"node": "get AI labels",
"type": "main",
"index": 0
}
]
]
},
"Gmail backfill": {
"main": [
[
{
"node": "Get labels",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Telegram: Train",
"type": "main",
"index": 0
}
]
]
},
"Telegram: Train": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Check Email",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Unwanted Email Output Parser",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Add rule for review": {
"main": [
[
{
"node": "Add AI recommended labels",
"type": "main",
"index": 0
}
]
]
},
"Message: Added rule": {
"main": [
[
{
"node": "Remove label: AI, Needs Review",
"type": "main",
"index": 0
}
]
]
},
"If low confidence...": {
"main": [
[
{
"node": "Add rule for review",
"type": "main",
"index": 0
}
],
[
{
"node": "Add AI recommended labels",
"type": "main",
"index": 0
}
]
]
},
"filter for AI labels": {
"main": [
[
{
"node": "Add label map ",
"type": "main",
"index": 0
}
]
]
},
"Get all pending rules": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Message: Deleted rule": {
"main": [
[
{
"node": "Remove label: AI, Needs Review",
"type": "main",
"index": 0
}
]
]
},
"set confidence threshold": {
"main": [
[
{
"node": "AI Check Email",
"type": "main",
"index": 0
}
]
]
},
"Add AI recommended labels": {
"main": [
[
{
"node": "Add fixed labels",
"type": "main",
"index": 0
}
]
]
},
"Unwanted Email Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Check Email",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Previous Rules and actions tool": {
"ai_tool": [
[
{
"node": "AI Check Email",
"type": "ai_tool",
"index": 0
}
]
]
},
"Remove label: AI, Needs Review": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Gmail backfill",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Get all labels",
"type": "main",
"index": 0
}
]
]
},
"add label & email metadata to ai output": {
"main": [
[
{
"node": "If low confidence...",
"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.
gmailOAuth2openAiApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
An AI-powered Gmail assistant built with n8n that automatically labels emails, learns from your decisions, and safely improves over time using human-in-the-loop training.
Source: https://n8n.io/workflows/15805/ — 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.
Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.
Automates SaaS operations by consolidating user management, AI-driven support triage, analytics, and billing into one unified system. User signups flow through registration, support requests route via
This workflow automatically transforms your messy inbox into a neatly organized space while ensuring you never miss a critical message. It connects to your Gmail account and triggers for every new ema
Automated Research Report Generation with OpenAI, Wikipedia, Google Search, and Gmail/Telegram. Uses lmChatOpenAi, memoryBufferWindow, toolHttpRequest, agent. Event-driven trigger; 26 nodes.
This workflow automates the process of generating professional research reports for researchers, students, and professionals. It eliminates manual research and report formatting by aggregating data, g