This workflow corresponds to n8n.io template #15246 — we link there as the canonical source.
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": "L10NyVYeXhXnkq39",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Zoho CRM - Data Quality Guardian",
"tags": [],
"nodes": [
{
"id": "08710672-102e-4641-88d6-ba503a40b7c9",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3824,
704
],
"parameters": {
"color": 7,
"width": 512,
"height": 512,
"content": "## Contact Data Validation\n\nThis section validates and standardizes key customer contact fields. Email addresses are cleaned and checked for format accuracy, while phone numbers are normalized into international structure for consistent storage, reporting and future communication workflows."
},
"typeVersion": 1
},
{
"id": "34120122-8478-4a88-a905-11883a008f9a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4368,
704
],
"parameters": {
"color": 7,
"width": 448,
"height": 512,
"content": "## Duplicate Detection & Enrichment\n\nThis section improves CRM accuracy by detecting possible duplicate records and enriching incomplete customer data. It helps prevent repeated leads, reduces database clutter and automatically fills missing company values for cleaner and more complete CRM records."
},
"typeVersion": 1
},
{
"id": "e21f01f0-81c1-4cc8-9a63-e011d149bf69",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4848,
704
],
"parameters": {
"color": 7,
"width": 304,
"height": 512,
"content": "## Quality Rules & Flagging\n\nThis section evaluates each CRM record against business quality rules and generates warning flags such as invalid email, invalid phone, duplicate entry or missing company information. It enables proactive data governance and cleaner CRM operations."
},
"typeVersion": 1
},
{
"id": "d366f741-23f7-47c3-ab0a-bd1430d2811b",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2976,
304
],
"parameters": {
"width": 752,
"height": 368,
"content": "## Workflow Overview & Setup Guide\n\nThis workflow automatically audits CRM records from Zoho CRM using a scheduled trigger. At the selected interval, it fetches leads or contacts and processes each record individually. Email addresses are validated and standardized, phone numbers are normalized, duplicate records are identified and missing company names are enriched.\n\nAfter processing, business rules assign quality flags such as invalid email, duplicate record, missing company or invalid phone. A reporting node generates a structured summary, which is then converted into a clean HTML report and automatically sent via email for easy review.\n\n**Recommended Setup Steps:**\n**1.** Connect your Zoho CRM credentials in n8n.\n**2.** Select Leads or Contacts module.\n**3.** Configure the Schedule Trigger (hourly, every 4 hours or daily).\n**4.** Configure Email (SMTP/Gmail) credentials.\n**5.** Test with a limited record batch first.\n**6.** Review summary reports.\n**7.** Adjust validation rules as business needs evolve."
},
"typeVersion": 1
},
{
"id": "b253810c-8bf4-489d-aad3-b7d3dc7da95e",
"name": "Sticky Note - Ingestion",
"type": "n8n-nodes-base.stickyNote",
"position": [
2976,
704
],
"parameters": {
"color": 7,
"width": 468,
"height": 508,
"content": "## CRM Data Intake & Trigger\n\nThis section automatically starts the workflow on a defined schedule and retrieves lead or contact records from Zoho CRM. It ensures CRM data is reviewed regularly for validation, duplicate detection, enrichment and quality monitoring without requiring manual execution."
},
"typeVersion": 1
},
{
"id": "d47e0e28-098b-444b-a497-7f2f0faf49d4",
"name": "Sticky Note - Normalization",
"type": "n8n-nodes-base.stickyNote",
"position": [
3472,
704
],
"parameters": {
"color": 7,
"width": 324,
"height": 508,
"content": "## Record Parsing & Preparation\n\nThis section converts CRM records into individual workflow items so each record can be processed independently. It ensures scalable handling of large datasets and prepares records for downstream validation and transformation logic."
},
"typeVersion": 1
},
{
"id": "98c25fd5-ca8b-4396-bd51-f1b97c03c1cc",
"name": "Fetch Zoho CRM Records",
"type": "n8n-nodes-base.zohoCrm",
"position": [
3264,
976
],
"parameters": {
"options": {},
"resource": "lead",
"operation": "getAll",
"returnAll": true
},
"credentials": {
"zohoOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "fd0b451b-f5ef-48d7-9292-033f4ae5359c",
"name": "Split Records for Processing",
"type": "n8n-nodes-base.code",
"position": [
3584,
976
],
"parameters": {
"jsCode": "const records = JSON.parse($json.records || '[]');\nreturn records.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "2c0a7dad-a579-40f4-b94d-d6d3b348dc32",
"name": "Validate Email Addresses",
"type": "n8n-nodes-base.code",
"position": [
3920,
976
],
"parameters": {
"jsCode": "return items.map(item => {\nconst email = (item.json.email || '').trim().toLowerCase();\n\nconst email_valid =\n/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) &&\n!email.includes('..');\n\nreturn {\njson:{\n...item.json,\nemail,\nemail_valid\n}\n};\n});"
},
"typeVersion": 2
},
{
"id": "970049ae-8c6a-4b21-b96d-b50941f17055",
"name": "Normalize Phone Numbers",
"type": "n8n-nodes-base.code",
"position": [
4128,
976
],
"parameters": {
"jsCode": "return items.map(item => {\n\nlet phone = (item.json.phone || '')\n.toString()\n.replace(/\\D/g,'');\n\nlet country='unknown';\n\nif(phone.length===10){\nphone='91'+phone;\ncountry='IN';\n}\nelse if(phone.startsWith('1')) country='US';\nelse if(phone.startsWith('44')) country='UK';\nelse if(phone.startsWith('971')) country='UAE';\nelse if(phone.startsWith('49')) country='DE';\nelse if(phone.startsWith('61')) country='AU';\n\nif(phone.length < 10 || phone.length > 15){\nphone='';\n}\n\nreturn {\njson:{\n...item.json,\nphone_standardized: phone,\ncountry\n}\n};\n});"
},
"typeVersion": 2
},
{
"id": "6fec4e7b-ff3e-42eb-a3e6-d78e9d503795",
"name": "Detect Duplicate Records",
"type": "n8n-nodes-base.code",
"position": [
4448,
976
],
"parameters": {
"jsCode": "const seen = {};\n\nreturn items.map(item => {\n\nconst email = item.json.email;\nconst phone = item.json.phone_standardized;\n\nconst key = email + \"|\" + phone;\n\nlet is_duplicate = false;\n\nif(seen[key]){\nis_duplicate = true;\n}else{\nseen[key] = true;\n}\n\nreturn {\njson:{\n...item.json,\nis_duplicate\n}\n};\n\n});"
},
"typeVersion": 2
},
{
"id": "33ccb61b-820f-400b-b155-43ce073b9748",
"name": "Enrich Missing Company Data",
"type": "n8n-nodes-base.code",
"position": [
4624,
976
],
"parameters": {
"jsCode": "return items.map(item => {\n\nlet company = item.json.company;\n\nif(!company || company.trim()===''){\n\nconst email = item.json.email || '';\n\nif(email.includes('@')){\ncompany = email.split('@')[1].split('.')[0];\ncompany =\ncompany.charAt(0).toUpperCase() +\ncompany.slice(1);\n}else{\ncompany='Unknown Company';\n}\n\n}\n\nreturn {\njson:{\n...item.json,\ncompany_enriched: company\n}\n};\n\n});"
},
"typeVersion": 2
},
{
"id": "f9113ab3-eaba-4490-92cf-b7a0d493a372",
"name": "Generate Quality Flags",
"type": "n8n-nodes-base.code",
"position": [
4944,
976
],
"parameters": {
"jsCode": "return items.map(item => {\n\nconst flags=[];\n\nif(!item.json.email_valid)\nflags.push('Invalid Email');\n\nif(!item.json.phone_standardized)\nflags.push('Invalid Phone');\n\nif(item.json.is_duplicate)\nflags.push('Duplicate');\n\nif(!item.json.company || item.json.company==='')\nflags.push('Missing Company');\n\nreturn {\njson:{\n...item.json,\ndata_quality_flags: flags,\nquality_score: 100 - (flags.length * 25)\n}\n};\n\n});"
},
"typeVersion": 2
},
{
"id": "6a235f60-3a14-465b-a2db-5b886fad0b2f",
"name": "Generate Quality Summary Report",
"type": "n8n-nodes-base.code",
"position": [
5296,
976
],
"parameters": {
"jsCode": "let total = items.length;\n\nlet summary = {\n total_records: total,\n valid_records: 0,\n invalid_email: 0,\n invalid_phone: 0,\n duplicates: 0,\n missing_company: 0\n};\n\nlet report = [];\n\nfor (const item of items) {\n const d = item.json;\n\n let isValid = true;\n\n // Email validation\n if (!d.email_valid) {\n summary.invalid_email++;\n isValid = false;\n }\n\n // Phone validation\n if (!d.phone_standardized) {\n summary.invalid_phone++;\n isValid = false;\n }\n\n // Duplicate check\n if (d.is_duplicate) {\n summary.duplicates++;\n isValid = false;\n }\n\n // Company check\n if (!d.company || d.company === '' || d.company === null) {\n summary.missing_company++;\n isValid = false;\n }\n\n if (isValid) {\n summary.valid_records++;\n }\n\n report.push({\n id: d.id,\n name: d.name,\n email: d.email,\n country: d.country || 'unknown',\n status: isValid ? 'VALID' : 'INVALID',\n flags: d.data_quality_flags || []\n });\n}\n\nreturn [\n {\n json: {\n summary,\n report\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "70f1c232-5eb9-4a89-97a3-073c884041b4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
5184,
704
],
"parameters": {
"color": 7,
"width": 368,
"height": 512,
"content": "## Reporting & Analytics\n\nThis section aggregates processed records into a summary report showing valid entries, duplicates, incomplete records and validation issues. It provides a quick operational view of CRM data health and helps teams track improvement over time."
},
"typeVersion": 1
},
{
"id": "140c559c-0d24-4085-8360-212b307ad6d5",
"name": "Automated Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3040,
976
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"triggerAtMinute": 5
}
]
}
},
"typeVersion": 1.3
},
{
"id": "9c47b7ef-b298-4344-852c-dd55845c3d51",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
5584,
704
],
"parameters": {
"color": 7,
"width": 368,
"height": 512,
"content": "## HTML Report Formatting\n\nThis section converts the generated JSON summary and record data into a structured and visually readable HTML report. It formats key metrics and detailed records into tables and sections, making the output suitable for email delivery and quick business review."
},
"typeVersion": 1
},
{
"id": "d55a7169-bd77-468e-be3b-ab6ebc021f2f",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
5984,
704
],
"parameters": {
"color": 7,
"width": 384,
"height": 512,
"content": "## Email Notification & Delivery\nThis section sends the formatted HTML report via email to designated recipients. It ensures stakeholders receive automated CRM data quality insights directly in their inbox on a scheduled basis, enabling timely monitoring and action."
},
"typeVersion": 1
},
{
"id": "6be3a895-046d-439c-b173-a4a640b727b9",
"name": "Format CRM Report to HTML",
"type": "n8n-nodes-base.code",
"position": [
5712,
976
],
"parameters": {
"jsCode": "const { summary, report } = items[0].json;\n\nlet rows = report.map(r => {\n const statusColor = r.status === 'VALID' ? '#16a34a' : '#dc2626';\n\n return `\n <tr>\n <td style=\"padding:8px;border:1px solid #ddd;\">${r.id || ''}</td>\n <td style=\"padding:8px;border:1px solid #ddd;\">${r.name || ''}</td>\n <td style=\"padding:8px;border:1px solid #ddd;\">${r.email || ''}</td>\n <td style=\"padding:8px;border:1px solid #ddd;\">${r.country || ''}</td>\n <td style=\"padding:8px;border:1px solid #ddd;font-weight:bold;color:${statusColor};\">\n ${r.status}\n </td>\n <td style=\"padding:8px;border:1px solid #ddd;\">\n ${(r.flags || []).join(', ') || '-'}\n </td>\n </tr>\n `;\n}).join('');\n\nconst html = `\n<div style=\"font-family: Arial, sans-serif; background:#f9fafb; padding:20px;\">\n \n <h2 style=\"color:#111827;\">\ud83d\udcca CRM Data Quality Report</h2>\n\n <div style=\"background:#ffffff;padding:15px;border-radius:8px;margin-bottom:20px;\">\n <h3 style=\"margin-top:0;\">Summary</h3>\n <p><strong>Total Records:</strong> ${summary.total_records}</p>\n <p><strong style=\"color:#16a34a;\">Valid Records:</strong> ${summary.valid_records}</p>\n <p><strong style=\"color:#dc2626;\">Invalid Emails:</strong> ${summary.invalid_email}</p>\n <p><strong style=\"color:#dc2626;\">Invalid Phones:</strong> ${summary.invalid_phone}</p>\n <p><strong style=\"color:#dc2626;\">Duplicates:</strong> ${summary.duplicates}</p>\n <p><strong style=\"color:#dc2626;\">Missing Company:</strong> ${summary.missing_company}</p>\n </div>\n\n <div style=\"background:#ffffff;padding:15px;border-radius:8px;\">\n <h3 style=\"margin-top:0;\">Details</h3>\n\n <table style=\"width:100%;border-collapse:collapse;font-size:14px;\">\n <thead>\n <tr style=\"background:#111827;color:#ffffff;\">\n <th style=\"padding:10px;border:1px solid #ddd;\">ID</th>\n <th style=\"padding:10px;border:1px solid #ddd;\">Name</th>\n <th style=\"padding:10px;border:1px solid #ddd;\">Email</th>\n <th style=\"padding:10px;border:1px solid #ddd;\">Country</th>\n <th style=\"padding:10px;border:1px solid #ddd;\">Status</th>\n <th style=\"padding:10px;border:1px solid #ddd;\">Flags</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n </tbody>\n </table>\n </div>\n\n <p style=\"margin-top:20px;font-size:12px;color:#6b7280;\">\n Generated automatically by n8n workflow\n </p>\n\n</div>\n`;\n\nreturn [{ json: { html } }];"
},
"typeVersion": 2
},
{
"id": "b17d66ab-47eb-4d2b-a8a9-81c3608ea176",
"name": "Send Data Quality Report Email",
"type": "n8n-nodes-base.gmail",
"position": [
6128,
976
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{$json.html}}",
"options": {
"appendAttribution": false
},
"subject": "Zoho CRM Data Quality Report"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "48dcd8b4-09fe-47ff-857a-a234fa61a4c2",
"connections": {
"Fetch Zoho CRM Records": {
"main": [
[
{
"node": "Split Records for Processing",
"type": "main",
"index": 0
}
]
]
},
"Generate Quality Flags": {
"main": [
[
{
"node": "Generate Quality Summary Report",
"type": "main",
"index": 0
}
]
]
},
"Normalize Phone Numbers": {
"main": [
[
{
"node": "Detect Duplicate Records",
"type": "main",
"index": 0
}
]
]
},
"Detect Duplicate Records": {
"main": [
[
{
"node": "Enrich Missing Company Data",
"type": "main",
"index": 0
}
]
]
},
"Validate Email Addresses": {
"main": [
[
{
"node": "Normalize Phone Numbers",
"type": "main",
"index": 0
}
]
]
},
"Format CRM Report to HTML": {
"main": [
[
{
"node": "Send Data Quality Report Email",
"type": "main",
"index": 0
}
]
]
},
"Automated Schedule Trigger": {
"main": [
[
{
"node": "Fetch Zoho CRM Records",
"type": "main",
"index": 0
}
]
]
},
"Enrich Missing Company Data": {
"main": [
[
{
"node": "Generate Quality Flags",
"type": "main",
"index": 0
}
]
]
},
"Split Records for Processing": {
"main": [
[
{
"node": "Validate Email Addresses",
"type": "main",
"index": 0
}
]
]
},
"Generate Quality Summary Report": {
"main": [
[
{
"node": "Format CRM Report to HTML",
"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.
gmailOAuth2zohoOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n workflow automatically audits your Zoho CRM leads on a schedule, cleans and validates emails and phone numbers, detects duplicates, enriches missing company data, generates a structured quality report and sends it as a styled HTML email. It helps maintain a clean,…
Source: https://n8n.io/workflows/15246/ — 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 automatically checks Zoho CRM every 5 minutes for newly created leads, enriches each lead using People Data Labs, evaluates its quality using Google Gemini (LLM Chain) and updates the le
This workflow automatically captures website visitors using Albacross, enriches their data with Datagma, and syncs it into HubSpot. Once leads are created or updated, a personalized email is generated
*Smartlead to HubSpot Performance Analytics A streamlined workflow to analyze your Smartlead performance metrics by tracking lifecycle stages in HubSpot and generating automated reports.*
This workflow automatically processes incoming Shopify/Gmail leads and pushes them into HubSpot as both Contacts and Deals.
This project automates the process of collecting and managing new leads submitted through a web form. It eliminates the need for manual data entry and ensures that each lead is: Properly recorded and