This workflow corresponds to n8n.io template #16690 — we link there as the canonical source.
This workflow follows the Gmail Trigger → Google Sheets 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": {
"templateCredsSetupCompleted": false
},
"name": "Gmail to Google Sheets Lead Tracker",
"tags": [],
"nodes": [
{
"id": "7f3426d1-b085-49c6-a960-c0d4e485db97",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
48
],
"parameters": {
"width": 480,
"height": 768,
"content": "## Gmail to Google Sheets Lead Tracker\n\n### How it works\n\nThis workflow monitors Gmail for new incoming messages and extracts lead information from each email. It checks the configured Google Sheet for matching existing leads, then only appends the lead when it is not already present. The flow is arranged left to right from email intake, through duplicate validation, to final sheet update.\n\n### Setup steps\n\n- Connect Gmail credentials for the trigger node and configure the mailbox, label, or search criteria that should produce new lead emails.\n- Connect Google Sheets credentials for both sheet nodes.\n- In the extraction code node, update the editable settings and parsing logic to match the lead fields in your email format.\n- Configure the Google Sheet ID, sheet/tab name, and column mappings for reading existing leads and appending new rows.\n\n### Customization\n\nAdjust the duplicate-check code to use the fields that uniquely identify a lead, such as email address, phone number, company, or a combination of values."
},
"typeVersion": 1
},
{
"id": "e4e385b2-7fb3-44bf-a098-0a22a1f07059",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
144
],
"parameters": {
"color": 7,
"width": 448,
"height": 320,
"content": "## Capture and extract lead\n\nStarts when a new Gmail message arrives and runs configurable code to extract lead details from the email content."
},
"typeVersion": 1
},
{
"id": "83d7d46d-0aa5-4593-a6f2-7943caeba900",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
160
],
"parameters": {
"color": 7,
"width": 688,
"height": 304,
"content": "## Validate against existing leads\n\nReads the current lead tracker from Google Sheets, compares the extracted lead against existing rows, and branches based on whether it is new."
},
"typeVersion": 1
},
{
"id": "2bd32099-9f54-4443-86a5-61f7c38747c0",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1216,
48
],
"parameters": {
"color": 7,
"width": 240,
"height": 336,
"content": "## Append new lead\n\nAdds the lead to the Google Sheet when the duplicate check determines it is a new lead."
},
"typeVersion": 1
},
{
"id": "22222222-2222-4222-8222-222222222201",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
0,
300
],
"parameters": {
"simple": true,
"filters": {
"q": "label:Leads",
"readStatus": "unread"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "22222222-2222-4222-8222-222222222202",
"name": "Extract Lead Details",
"type": "n8n-nodes-base.code",
"position": [
260,
300
],
"parameters": {
"jsCode": "// ===== EDIT YOUR SETTINGS HERE =====\nconst GOOGLE_SHEET_ID = 'your-google-sheet-id';\nconst SHEET_NAME = 'Leads';\n\nconst email = $input.first().json;\n\nconst fromValue = email.from?.value?.[0] || email.from || {};\nconst senderEmail =\n typeof fromValue === 'string'\n ? fromValue.match(/[\\w.+-]+@[\\w.-]+\\.\\w+/)?.[0] || fromValue\n : fromValue.address || '';\nconst senderName =\n typeof fromValue === 'string'\n ? fromValue.replace(senderEmail, '').replace(/[<>\"]/g, '').trim()\n : fromValue.name || '';\n\nconst subject = email.subject || '';\nconst body = email.textPlain || email.text || email.snippet || '';\nconst messageId = email.id || email.messageId || '';\nconst receivedAt = email.date || new Date().toISOString();\n\nconst phoneMatch = body.match(/(\\+?\\d[\\d\\s\\-().]{7,}\\d)/);\nconst phone = phoneMatch ? phoneMatch[1].trim() : '';\n\nreturn [\n {\n json: {\n googleSheetId: GOOGLE_SHEET_ID,\n sheetName: SHEET_NAME,\n email: senderEmail,\n name: senderName,\n subject,\n body: body.slice(0, 500),\n phone,\n messageId,\n receivedAt,\n },\n },\n];"
},
"typeVersion": 2
},
{
"id": "22222222-2222-4222-8222-222222222203",
"name": "Read Leads in Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
520,
300
],
"parameters": {
"options": {},
"resource": "sheet",
"operation": "read",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $json.sheetName }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.googleSheetId }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "22222222-2222-4222-8222-222222222204",
"name": "Verify Unique Lead",
"type": "n8n-nodes-base.code",
"position": [
780,
300
],
"parameters": {
"jsCode": "const lead = $('Extract Lead Details').first().json;\nconst rows = $input.all();\n\nconst isDuplicate = rows.some((item) => {\n const row = item.json;\n const existingEmail = row.Email || row.email || row['Email Address'] || '';\n const existingMessageId = row['Message ID'] || row.messageId || row.MessageId || '';\n\n if (existingMessageId && lead.messageId && existingMessageId === lead.messageId) {\n return true;\n }\n\n return (\n existingEmail &&\n lead.email &&\n String(existingEmail).toLowerCase() === String(lead.email).toLowerCase()\n );\n});\n\nreturn [\n {\n json: {\n ...lead,\n isDuplicate,\n },\n },\n];"
},
"typeVersion": 2
},
{
"id": "22222222-2222-4222-8222-222222222205",
"name": "Determine New Lead Status",
"type": "n8n-nodes-base.if",
"position": [
1020,
300
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "is-new-lead-check",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.isDuplicate }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.2
},
{
"id": "22222222-2222-4222-8222-222222222206",
"name": "Add Lead to Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
1260,
220
],
"parameters": {
"columns": {
"value": {
"Name": "={{ $json.name }}",
"Email": "={{ $json.email }}",
"Phone": "={{ $json.phone }}",
"Message": "={{ $json.body }}",
"Subject": "={{ $json.subject }}",
"Message ID": "={{ $json.messageId }}",
"Received At": "={{ $json.receivedAt }}"
},
"schema": [
{
"id": "Email",
"type": "string",
"display": true,
"required": false,
"displayName": "Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Subject",
"type": "string",
"display": true,
"required": false,
"displayName": "Subject",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Phone",
"type": "string",
"display": true,
"required": false,
"displayName": "Phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Received At",
"type": "string",
"display": true,
"required": false,
"displayName": "Received At",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Message ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": []
},
"options": {},
"resource": "sheet",
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $json.sheetName }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.googleSheetId }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "gmail-leads-v1",
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Extract Lead Details",
"type": "main",
"index": 0
}
]
]
},
"Verify Unique Lead": {
"main": [
[
{
"node": "Determine New Lead Status",
"type": "main",
"index": 0
}
]
]
},
"Extract Lead Details": {
"main": [
[
{
"node": "Read Leads in Sheets",
"type": "main",
"index": 0
}
]
]
},
"Read Leads in Sheets": {
"main": [
[
{
"node": "Verify Unique Lead",
"type": "main",
"index": 0
}
]
]
},
"Determine New Lead Status": {
"main": [
[
{
"node": "Add Lead to Sheets",
"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.
gmailOAuth2googleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow polls Gmail for new unread emails with a specific label, extracts lead details from each message, checks Google Sheets for duplicates by email or message ID, and appends only new leads to a designated spreadsheet. Polls Gmail every minute for unread messages…
Source: https://n8n.io/workflows/16690/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow allows users to extract potential leads from their inboxes. The idea of a reverse outreach is based on the notion that the next big client/customer/partner might be sitting in your inbox
This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em
This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese
This workflow finds local businesses from Google Maps and automatically enriches them with emails, social profiles, AI summaries, and personalized outreach messages — all saved to Google Sheets. Searc
This workflow leverages n8n to perform automated Google Maps API queries and manage data efficiently in Google Sheets. It's designed to extract specific location data based on a given list of ZIP code