AutomationFlowsAI & RAG › Automatic Gmail Unsubscribe Detection with AI and Google Sheets Contact…

Automatic Gmail Unsubscribe Detection with AI and Google Sheets Contact…

Original n8n title: Automatic Gmail Unsubscribe Detection with AI and Google Sheets Contact Management

ByItunu @e2nu on n8n.io

This template is designed for freelancers, lead generation specialists, and outreach managers; particularly those running email outreach campaigns for clients or personal lead-gen projects.

Event trigger★★★★☆ complexityAI-powered22 nodesGmail TriggerOpenAI ChatChain LlmGoogle SheetsGmail
AI & RAG Trigger: Event Nodes: 22 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10351 — 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 →

Download .json
{
  "id": "1q5Uq1CaoW5RqKlU",
  "name": "UNSUBSCRIBE MANAGER FOR LEAD GEN V.3.0",
  "tags": [
    {
      "id": "vLLcwhiSm3lgtjpI",
      "name": "PORTFOLIO",
      "createdAt": "2025-10-30T14:01:29.686Z",
      "updatedAt": "2025-10-30T14:01:29.686Z"
    }
  ],
  "nodes": [
    {
      "id": "d11fc570-173b-4584-b733-884ad7f0b6c7",
      "name": "Watch New replies",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -448,
        160
      ],
      "parameters": {
        "filters": {
          "includeSpamTrash": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 5
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.3
    },
    {
      "id": "3f255b99-90dc-49e9-9f6e-fe312dde0bfa",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        384,
        272
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 20,
          "temperature": 0.1
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2ebd3350-9ae9-462c-883e-df3aa08f8d84",
      "name": "DETECT INTENT",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        256,
        128
      ],
      "parameters": {
        "text": "=You are a message intent detector for outreach emails. \nYour job is to determine if this message means the sender wants to stop receiving emails or unsubscribe.\n\nReply strictly with one word:\n- \"unsubscribe\" \u2192 if they indicate they don't want further contact (even indirectly)\n- \"keep\" \u2192 if they are interested, neutral, or unrelated.\n\nMessage:\n{{ $json.snippet }}",
        "batching": {},
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.7
    },
    {
      "id": "12d530c3-e5fa-4a8b-bd40-3194789d54ba",
      "name": "NORMALIZE AI OUTPUT",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        128
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  let aiResponse = \"\";\n\n  const json = item.json;\n  if (json.message?.[0]?.content) {\n    aiResponse = json.message[0].content;\n  } else if (json.data) {\n    aiResponse = json.data;\n  } else if (json.choices?.[0]?.message?.content) {\n    aiResponse = json.choices[0].message.content;\n  } else if (json.text) {\n    aiResponse = json.text;\n  }\n\n  aiResponse = aiResponse?.trim().toLowerCase() || \"\";\n\n  const isUnsubscribe =\n    aiResponse.includes(\"unsubscribe\") ||\n    aiResponse.includes(\"remove me\") ||\n    aiResponse.includes(\"opt out\") ||\n    aiResponse.includes(\"stop emailing\") ||\n    aiResponse.includes(\"do not contact\");\n\n  return {\n    json: {\n      ...json,\n      aiResponse,\n      isUnsubscribe\n    }\n  };\n});"
      },
      "retryOnFail": true,
      "typeVersion": 2
    },
    {
      "id": "097760ab-a7f7-4c68-956c-7f2f3743d48d",
      "name": "CHECK MAIN SHEET",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        224,
        576
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ \n  $('Watch New replies').item.json.From.match(/<([^>]+)>/)?.[1]?.trim().toLowerCase() || \n  $('Watch New replies').item.json.From.trim().toLowerCase() \n}}",
              "lookupColumn": "EMAIL"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "3c7f3275-2562-4a74-8dd9-46bb6f21a5cc",
      "name": "Delete rows or columns from sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        448,
        576
      ],
      "parameters": {
        "operation": "delete",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "startIndex": "={{ $json.row_number }}",
        "authentication": "serviceAccount"
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "e6f988b5-3d8d-4113-bccf-9df91f1e92c1",
      "name": "ADD TO UNSUBSCRIBE",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1312,
        240
      ],
      "parameters": {
        "columns": {
          "value": {
            "DATE": "={{ new Date().toISOString().split('T')[0] }}",
            "EMAIL": "={{ $('IS EMAIL THERE?').first().json.Email }}",
            "WHERE": "UNSUBSCRIBE MESSAGE REPLY",
            "MESSAGE": "={{ $('Watch New replies').item.json.snippet }}"
          },
          "schema": [
            {
              "id": "EMAIL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "EMAIL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "DATE",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "DATE",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WHERE",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "WHERE",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "MESSAGE",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "MESSAGE",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "authentication": "serviceAccount"
      },
      "executeOnce": false,
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "1fb3adb5-cf01-41f7-980a-d366013e894f",
      "name": "ADD UNSUBSCRIBE TO EMAIL",
      "type": "n8n-nodes-base.gmail",
      "position": [
        752,
        576
      ],
      "parameters": {
        "labelIds": [
          "Label_2476288409640151649"
        ],
        "messageId": "={{ $('Watch New replies').item.json.id }}",
        "operation": "addLabels"
      },
      "retryOnFail": true,
      "typeVersion": 2.1
    },
    {
      "id": "68fd2508-7c12-4121-b7a0-1fb18fd81893",
      "name": "Get unsubscribe sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1088,
        48
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "your-google-sheet-id"
        },
        "authentication": "serviceAccount"
      },
      "retryOnFail": true,
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "94e94f3c-1484-4ba4-8b2c-0747f94c605c",
      "name": "IS EMAIL THERE?",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        48
      ],
      "parameters": {
        "jsCode": "//----------------------------------------------------\n// Inputs\n//----------------------------------------------------\nconst unsubscribed = $items(\"Get unsubscribe sheet\").map(i => \n  (i.json.Email || \"\").toLowerCase().trim()\n);\nconst currentEmail = $('Watch New replies').item.json.From\n  .match(/<([^>]+)>/)?.[1]?.toLowerCase().trim() || \n  $('Watch New replies').item.json.From.toLowerCase().trim();\n\n//----------------------------------------------------\n// Check and decide\n//----------------------------------------------------\nif (!unsubscribed.includes(currentEmail)) {\n  return [{ json: { Email: currentEmail}}];\n} else {\n  return []; // Skip if already in the sheet\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "7eb58195-bbaf-479b-83b9-2cf31323d6b4",
      "name": "IF IT ALREADY EXISTS, DO NOTHING",
      "type": "n8n-nodes-base.if",
      "position": [
        1088,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Email }}",
              "rightValue": "={{ $('Get unsubscribe sheet').item.json.EMAIL }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4b885ba7-1ef6-4393-a91f-bf881c9f90ce",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1312,
        -384
      ],
      "parameters": {
        "width": 816,
        "height": 1168,
        "content": "## UNSUBSCRIBE MANAGER\n**This workflow helps manage your leads and email deliverability health by removing contacts that have unsubscribed from your outreach list automatically.**\n\n\n\n**Who is this for?**\nFor marketers, freelancers, basically anyone that does lead generation and outreach via emails and need to keep their list clean and email deliverability healthy and strong.\n\n\n\n\n**How it works**\nThis works in 6 simple steps; starting from receiving an email to the AI node -> to automatically reading the content and determining if the prospect is interested or needs to be removed from your list---> to finally removing from your main sheet and adding an unsubscribe tag to that contact in your Gmail.\n\n\n**More Info**\n\n**Step 3**\nHere is the prompt to be used in the AI node; this can be edited.\n\n**AI PROMPT**\n_You are a message intent detector for outreach emails._\n_Your job is to determine if this message means the sender wants to stop receiving emails or unsubscribe._\n\n_Reply strictly with one word:_\n_- \"unsubscribe\" \u2192 if they indicate they don't want further contact (even indirectly)_\n_- \"keep\" \u2192 if they are interested, neutral, or unrelated._\n\n_Message:_\n_{{ $json.snippet }}_\n\nThe {{ $json.snippet }} is gotten from the Gmail node.\n\n\n**NORMALIZE AI OUTPUT**\nWe can't always trust the response of AI, so the **_Normalize AI Output_** node is a safe guard to ensure the two words we need are strictly passed on to the next node. The code is in the node.\n\n   \n\n**Steps 4&5**\n\n**IS EMAIL THERE?**\nThis checks the email from Gmail against your unsubscribe sheet to ensure it isn't already there. The code is in the node. This is one way of doing the checks, the other simple way is using the _IF_ node as was used in the _CHECK MAIN SHEET_ node.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b5ba836c-2941-446f-a3ed-32359fc06f25",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 432,
        "content": "## Step 2 (Optional)\n**Exclude any emails you usually receive/personal emails.** \n"
      },
      "typeVersion": 1
    },
    {
      "id": "7e74f9e0-7764-4404-8d28-08e2db82f295",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1552,
        144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "65a72f4e-b85b-4b7d-b191-601a3a2a53fd",
      "name": "No Operation, do nothing1",
      "type": "n8n-nodes-base.noOp",
      "position": [
        944,
        576
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "74e491a2-322b-4558-89d6-c78b9e64bf47",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 432,
        "content": "## Step 1\n**GMAIL Trigger watches for new replies** "
      },
      "typeVersion": 1
    },
    {
      "id": "297fdd22-5c8a-4b39-841a-1a4429e77018",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 432,
        "content": "## Step 3\n**The AI checks the response and summaries in two words ** \"Unsubscribe\" or \"Keep\"  and ensures the output comes out as \"unsubscribe or keep** "
      },
      "typeVersion": 1
    },
    {
      "id": "77774adc-abc6-4302-97ec-12f3251ad204",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 432,
        "content": "## Step 4\n**If AI says \"unsubscribe\", check if the email is not already in the unsubscribe sheet and then add it.** "
      },
      "typeVersion": 1
    },
    {
      "id": "fe0a8ec7-b8de-4a77-9cd2-c4e500b44904",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 352,
        "content": "## Step 5\n**Check and remove from the main sheet** "
      },
      "typeVersion": 1
    },
    {
      "id": "f37ebb6f-76ad-4b17-ac28-33bbaf17c170",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 352,
        "content": "## Step 6\n**Add \"Unsubscribe\" tag to email on Gmail** "
      },
      "typeVersion": 1
    },
    {
      "id": "c4f3b118-d38c-4695-9dbe-82cb272cca95",
      "name": "UNSUBSCRIBE?",
      "type": "n8n-nodes-base.if",
      "position": [
        848,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.text }}",
              "rightValue": "unsubscribe"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "335c9d2d-022e-46a9-964a-9052c59f3c46",
      "name": "Personal Emails?",
      "type": "n8n-nodes-base.if",
      "position": [
        -64,
        160
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.From }}",
              "rightValue": "=your personal email"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2468f2da-5bfe-4a8d-b9b0-48bb576eb2e5",
  "connections": {
    "UNSUBSCRIBE?": {
      "main": [
        [
          {
            "node": "Get unsubscribe sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DETECT INTENT": {
      "main": [
        [
          {
            "node": "NORMALIZE AI OUTPUT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IS EMAIL THERE?": {
      "main": [
        [
          {
            "node": "IF IT ALREADY EXISTS, DO NOTHING",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CHECK MAIN SHEET": {
      "main": [
        [
          {
            "node": "Delete rows or columns from sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Personal Emails?": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "DETECT INTENT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "DETECT INTENT",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Watch New replies": {
      "main": [
        [
          {
            "node": "Personal Emails?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ADD TO UNSUBSCRIBE": {
      "main": [
        [
          {
            "node": "CHECK MAIN SHEET",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NORMALIZE AI OUTPUT": {
      "main": [
        [
          {
            "node": "UNSUBSCRIBE?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get unsubscribe sheet": {
      "main": [
        [
          {
            "node": "IS EMAIL THERE?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ADD UNSUBSCRIBE TO EMAIL": {
      "main": [
        [
          {
            "node": "No Operation, do nothing1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF IT ALREADY EXISTS, DO NOTHING": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "ADD TO UNSUBSCRIBE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete rows or columns from sheet": {
      "main": [
        [
          {
            "node": "ADD UNSUBSCRIBE TO EMAIL",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This template is designed for freelancers, lead generation specialists, and outreach managers; particularly those running email outreach campaigns for clients or personal lead-gen projects.

Source: https://n8n.io/workflows/10351/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

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.

Gmail Trigger, N8N Nodes Pdf Api Hub, Google Sheets +4
AI & RAG

AI Agents Vs AI Workflow. Uses lmChatOpenAi, gmailTrigger, gmail, gmailTool. Event-driven trigger; 30 nodes.

OpenAI Chat, Gmail Trigger, Gmail +7
AI & RAG

What this workflow does:

Gmail Trigger, OpenAI Chat, Google Sheets +6
AI & RAG

This workflow automates document processing using LlamaParse to extract and analyze text from various file formats. It intelligently processes documents, extracts structured data, and delivers actiona

Gmail, Gmail Trigger, HTTP Request +6
AI & RAG

The workflow runs every hour with a randomized delay of 5–20 minutes to help distribute load. It records the exact date and time a lead is emailed so you can track outreach. Follow-ups are automatical

Google Sheets, Agent, OpenAI Chat +5