This workflow corresponds to n8n.io template #4961 — we link there as the canonical source.
This workflow follows the Gmail → 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 →
{
"id": "LWUeLMrYSdCmTIl2",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "My workflow",
"tags": [],
"nodes": [
{
"id": "5286306a-9e2c-4e3c-9f3c-9d4ef7820c0c",
"name": "Google Sheets Trigger",
"type": "n8n-nodes-base.googleSheetsTrigger",
"position": [
0,
0
],
"parameters": {
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1920008147,
"cachedResultUrl": "",
"cachedResultName": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1m4lbSUudsxNI-thsPBZ1sjMU2yWLSQf0MrURDjBwIlg",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "40bd5635-03d9-40fa-9f14-c8a724838c7f",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
900,
-120
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "ff50f240-ed69-4561-9831-a08fca231788",
"name": "Replace Me",
"type": "n8n-nodes-base.noOp",
"position": [
920,
140
],
"parameters": {},
"typeVersion": 1
},
{
"id": "dd49af1e-0bc6-4667-a59a-ff43c2adfc84",
"name": "LEDGERS",
"type": "@ledgers/n8n-nodes-ledgers-cloud.ledgers",
"position": [
1340,
-140
],
"parameters": {
"contactName": "={{ $json['Name'] }}",
"additionalFields": {
"email": "={{ $json['Email'] }}",
"mobile": "={{ $json['Mobile'] }}"
}
},
"credentials": {
"ledgersApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "08263687-a6f4-4008-95ef-b625119d475d",
"name": "Contact Name Error Mail Trigger",
"type": "n8n-nodes-base.gmail",
"position": [
0,
220
],
"parameters": {
"sendTo": "",
"message": "=<p>Contact Name is Missing<br>\nrow no: {{ $json['row_number'] }}",
"options": {},
"subject": "Contact Creation Failure"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"notesInFlow": false,
"typeVersion": 2.1
},
{
"id": "8d02c452-50d4-4626-ac4e-38db7796e15c",
"name": "Contact Name Validation",
"type": "n8n-nodes-base.if",
"position": [
220,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2af93c46-69d5-468c-a73c-ebf9ee39d28b",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json['Name'] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f5d57159-4ecc-422f-a7c2-74c96c921d6a",
"name": "Email & Mobile Format Checker",
"type": "n8n-nodes-base.code",
"position": [
440,
-100
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const email = String($json.Email || '');\nconst regex_email = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst mobile = String($json.Mobile || '');\nconst regex_mobile = /^(\\+\\d{1,3}[- ]?)?\\d{10}$/;\n\nlet validEmail = false;\n\nif (!email) {\n validEmail = true; // Treat empty as valid\n} else if (regex_email.test(email)) {\n validEmail = true; // Matches regex\n} else {\n validEmail = false; // Invalid format\n}\n\nlet validMobile = false;\n\nif (!mobile) {\n validMobile = true; // Treat empty as valid\n} else if (regex_mobile.test(mobile)) {\n validMobile = true; // Matches regex\n} else {\n validMobile = false; // Invalid format\n}\n\nreturn {\n json: {\n ...$json,\n validEmail: validEmail,\n validMobile:validMobile\n }\n};"
},
"notesInFlow": false,
"typeVersion": 2
},
{
"id": "51a32d91-3d6a-4a3d-922d-28a681579b02",
"name": "Email & Mobile Validator",
"type": "n8n-nodes-base.if",
"position": [
660,
-100
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c3ce425a-a354-4aad-8edd-3b483c135f8b",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[validEmail] }}",
"rightValue": ""
},
{
"id": "5728ac2b-d0c0-47a8-963e-5db64fe81429",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[validMobile] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b7d48ba7-6b06-495d-be50-ecc6bf2ef0fb",
"name": "Email/Mobile Error Mail Trigger",
"type": "n8n-nodes-base.gmail",
"position": [
440,
120
],
"parameters": {
"sendTo": "",
"message": "=<p>Enter Valid Email/Mobile for contact: <b>{{ $json['Name'] }}</b></p>",
"options": {},
"subject": "Contact Creation Failure"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 2.1
},
{
"id": "188e5335-f3cf-4044-bd7a-804c393e53df",
"name": "Mobile Formatter",
"type": "n8n-nodes-base.code",
"position": [
1120,
-140
],
"parameters": {
"jsCode": "const mobileRaw = String($json.Mobile || '').trim();\n\nlet mobile_country_code = '';\nlet mobile = '';\n\nif (mobileRaw) {\n if (mobileRaw.includes('-')) {\n // Split by -\n const parts = mobileRaw.split('-').map(p => p.trim());\n\n if (parts.length >= 2) {\n mobile_country_code = parts[0];\n mobile = parts.slice(1).join(' ');\n\n // Ensure country code starts with +\n if (!mobile_country_code.startsWith('+')) {\n mobile_country_code = '+' + mobile_country_code;\n }\n }\n } else {\n // No dash, treat as mobile\n mobile = mobileRaw;\n }\n}\n\nreturn {\n json: {\n mobile_country_code,\n mobile\n }\n};"
},
"typeVersion": 2
},
{
"id": "9a91c0ae-dddb-4a56-b956-50c357c1a279",
"name": "Contact Creation Failure Mail Trigger",
"type": "n8n-nodes-base.gmail",
"position": [
1160,
120
],
"parameters": {
"sendTo": "",
"message": "=Error while creating a contact from the row {{ $json['row_number'] }}\nError: {{ $json['errorMessage'] }}",
"options": {},
"subject": "Contact Creation Failure",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "319837cb-0799-4b3b-a528-80ede79b2a90",
"name": "Contact Creation Validator",
"type": "n8n-nodes-base.if",
"position": [
1560,
-140
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2da856b9-b6ec-4630-b985-3c6c30a06b76",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json['errorCode'] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9fd8b8c5-d9bb-4cf6-a38f-7dc9e5ff366b",
"name": "Get Created Time",
"type": "n8n-nodes-base.code",
"position": [
1780,
-160
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const date = new Date();\nconst formatted = date.getDate().toString().padStart(2, '0') + '-' +\n (date.getMonth() + 1).toString().padStart(2, '0') + '-' +\n date.getFullYear() + ' ' +\n date.getHours().toString().padStart(2, '0') + ':' +\n date.getMinutes().toString().padStart(2, '0') + ':' +\n date.getSeconds().toString().padStart(2, '0');\n\nreturn {\n json: {\n ...$json,\n created_at: formatted\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "87d9c064-ade5-451a-9290-8acfc62f123c",
"name": "Update Sheet Contact Creation",
"type": "n8n-nodes-base.googleSheets",
"position": [
1800,
120
],
"parameters": {
"columns": {
"value": {
"Time": "={{ $json['created_at'] }}",
"Contact ID": "={{ $json['contact_id'] }}"
},
"schema": [
{
"id": "Time",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Time",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Contact ID",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Contact ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Contact ID"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "",
"cachedResultName": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1b1bxz-j9tgUtsl-_1kkoEs30WyF5r3xQ2fd49JmHTwo",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d3b1397a-d068-4fe1-95af-6777351d3cbf",
"connections": {
"LEDGERS": {
"main": [
[
{
"node": "Contact Creation Validator",
"type": "main",
"index": 0
}
]
]
},
"Replace Me": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Mobile Formatter",
"type": "main",
"index": 0
}
],
[
{
"node": "Replace Me",
"type": "main",
"index": 0
}
]
]
},
"Get Created Time": {
"main": [
[
{
"node": "Update Sheet Contact Creation",
"type": "main",
"index": 0
}
]
]
},
"Mobile Formatter": {
"main": [
[
{
"node": "LEDGERS",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets Trigger": {
"main": [
[
{
"node": "Contact Name Validation",
"type": "main",
"index": 0
}
]
]
},
"Contact Name Validation": {
"main": [
[
{
"node": "Email & Mobile Format Checker",
"type": "main",
"index": 0
}
],
[
{
"node": "Contact Name Error Mail Trigger",
"type": "main",
"index": 0
}
]
]
},
"Email & Mobile Validator": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Email/Mobile Error Mail Trigger",
"type": "main",
"index": 0
}
]
]
},
"Contact Creation Validator": {
"main": [
[
{
"node": "Get Created Time",
"type": "main",
"index": 0
}
],
[
{
"node": "Contact Creation Failure Mail Trigger",
"type": "main",
"index": 0
}
]
]
},
"Email & Mobile Format Checker": {
"main": [
[
{
"node": "Email & Mobile Validator",
"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.
gmailOAuth2googleSheetsOAuth2ApigoogleSheetsTriggerOAuth2ApiledgersApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Before Using this Template first search LEDGERS in the nodes list and Update!
Source: https://n8n.io/workflows/4961/ — 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.
Automatically processes new orders added to Google Sheets. Small orders are approved instantly; large orders trigger an HTML email with one-click Approve / Reject links — each handled by an independen
General use cases include: Property managers who manage multiple buildings or units. Building owners looking to centralize tenant repair communication. Automation builders who want to learn multi-trig
FlujoDeReservasUpdated. Uses googleSheetsTrigger, googleSheets, googleCalendar, gmail. Event-driven trigger; 27 nodes.
This workflow automates the full offer letter lifecycle, from generation to final candidate response tracking. When a new row with a Pending status is added to Google Sheets, it creates a personalized
Hiring teams often struggle with document follow-ups, offer letter generation, and stakeholder communication. Manual checks, email back-and-forth, and missing files slow down hiring and create chaos d