{
  "id": "D1FrvXZykBqu21rH",
  "name": "My workflow",
  "tags": [],
  "nodes": [
    {
      "id": "55bef248-1f8f-4c7a-85b5-51fcc9232c19",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -672,
        1136
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "74bb545a-30c1-4822-b6e9-f7640d45b912",
      "name": "Get many messages",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -464,
        1136
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "sender": "user@example.com",
          "readStatus": "unread",
          "includeSpamTrash": true
        },
        "options": {},
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7d6616c4-6535-406c-b705-08a241a339ef",
      "name": "Extracting Preheader From HTML",
      "type": "n8n-nodes-base.html",
      "position": [
        0,
        1248
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "dataPropertyName": "html",
        "extractionValues": {
          "values": [
            {
              "key": "preheader",
              "cssSelector": "div#emailPreHeader"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6396dbe1-4a71-4692-85c5-ba429a3e70aa",
      "name": "Combine HTML, Expected Content and Preheader",
      "type": "n8n-nodes-base.merge",
      "position": [
        320,
        1120
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "620a25d1-d472-4bc9-8991-7fcfaf3fc427",
      "name": "Extract Actual Content and Results",
      "type": "n8n-nodes-base.code",
      "position": [
        608,
        1120
      ],
      "parameters": {
        "jsCode": "function cleanHtml(str) {\n  return str\n    .replace(/<style[\\s\\S]*?<\\/style>/gi, \"\")\n    .replace(/<head[\\s\\S]*?<\\/head>/gi, \"\")\n    .replace(/<footer[\\s\\S]*?<\\/footer>/gi, \"\")\n    .replace(/<[^>]+>/g, \" \")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\nfunction decodeEntities(str) {\n  return str\n    .replace(/&#x9;/g, \" \")\n    .replace(/&amp;/g, \"&\")\n    .replace(/&nbsp;/g, \" \")\n    .trim();\n}\n\n// Expected values from Excel\nconst excelRows = items.filter(i => i.json[\"Expected\"]);\n\n// Gmail node output\nconst gmail = items.find(i => i.json.subject || i.json.from || i.json.textAsHtml || i.json.text)?.json || {};\nconst fromAddress = gmail.from?.value?.[0]?.address || \"\";\nconst replyToAddress = gmail.replyTo?.value?.[0]?.address || \"\";\nconst subjectLine = (gmail.subject || \"\").replace(/^TEST\\s*\\|\\s*/i, \"\").trim();\n\n// Extract HTML Content node output (preheader)\nconst preheaderItem = items.find(i => i.json.preheader)?.json || {};\nconst preheader = preheaderItem.preheader || \"\";\n\n// Body Copy\nlet bodyCopy = \"\";\nif (gmail.textAsHtml) {\n  bodyCopy = cleanHtml(gmail.textAsHtml);\n  bodyCopy = decodeEntities(bodyCopy);\n} else if (gmail.text) {\n  bodyCopy = decodeEntities(gmail.text.trim());\n}\n\n// Validation logic\nconst results = excelRows.map(row => {\n  const expected = String(row.json[\"Expected\"] || \"\");\n  let actualValue = \"\";\n\n  if (expected.includes(\"@\")) {\n    actualValue = fromAddress || replyToAddress;\n  } else if (expected.toLowerCase().includes(\"subject\")) {\n    actualValue = subjectLine;\n  } else if (expected.toLowerCase().includes(\"preheader\")) {\n    actualValue = preheader;\n  } else {\n    actualValue = bodyCopy;\n  }\n\n  const match = expected.trim() === String(actualValue).trim() ? \"MATCH\" : \"MISMATCH\";\n  return { Expected: expected, Actual: actualValue, Result: match };\n});\n\nreturn results.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "34805684-af93-454c-850f-9c8fc3aa0c2b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        848
      ],
      "parameters": {
        "width": 832,
        "height": 624,
        "content": "## Main Overview \u2013 Email Marketing Automation QA Workflow\n\nThis workflow automates the end\u2011to\u2011end content verification process for email QA by comparing expected content values stored in Google Sheets with the actual values extracted from the email HTML. It eliminates manual checks for key email elements\u2014including From Address, Subject Line, Preheader, Body Copy, and Footer Content\u2014and generates a clear Result (Pass/Fail) for each item. This ensures consistency, accuracy, and significantly reduces QA turnaround time.\n\n**How it works**\n1. Load all expected content values (From, Subject, Preheader, Body, Footer) from Google Sheets.\n2. Extract actual content from the email HTML using HTML parsing + JavaScript logic.\n3. Merge expected and actual entries using a unique key (such as Section ID).\n4. Compare Expected vs Actual values to determine the Result (Pass/Fail).\n5. Write the Actual Content + Result back into Google Sheets, forming a structured QA output.\n\n**Setup steps**\n* Connect Google Sheets (Expected Content Source).\n* Configure Gmail / HTML Source to pull the email HTML.\n* Update JS extraction logic to match your email template structure.\n* Ensure proper row matching via Section ID / Labels."
      },
      "typeVersion": 1
    },
    {
      "id": "c8074a02-05b1-4924-a07f-727dc816120e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        880
      ],
      "parameters": {
        "color": 7,
        "width": 1168,
        "height": 592,
        "content": "## Content Check \nReads expected email elements from Google Sheets\u2014From address, Subject line, Preheader, Body copy, and Footer content. Extracts actual values from HTML, merges inputs, compares expected vs actual to generate the Result, and logs Actual Content + Result back to Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "58537ad7-fc2a-4625-8e51-e81d2b290ed7",
      "name": "Log Content Checks to Excel",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        848,
        1120
      ],
      "parameters": {
        "columns": {
          "value": {
            "Label": "={{ $json.Label }}",
            "Actual": "={{ $json.Actual }}",
            "Result": "={{ $json.Result }}",
            "Expected": "={{ $json.Expected }}"
          },
          "schema": [
            {
              "id": "Label",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Label",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Expected",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Expected",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Actual",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Actual",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Result",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Mismatch Reason",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Mismatch Reason",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Expected"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "=// Parameters provided by user in node config\nconst sheetId = this.getNodeParameter(\"sheetId\", 0) || \"YOUR_SPREADSHEET_ID\";\nconst sheetName = this.getNodeParameter(\"sheetName\", 0) || \"Sheet1\";\n\nconst { google } = require(\"googleapis\");\nconst sheets = google.sheets({ version: \"v4\", auth });\n\nconst response = await sheets.spreadsheets.values.get({\n  spreadsheetId: sheetId,\n  range: sheetName,\n});\n\nreturn response.data.values.map(row => ({ json: { row } }));"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "=https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5b8662c9-8e5b-493e-bd66-85d840b44acd",
      "name": "Load Expected Content",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        1008
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "=// Parameters provided by user in node config\nconst sheetId = this.getNodeParameter(\"sheetId\", 0) || \"YOUR_SPREADSHEET_ID\";\nconst sheetName = this.getNodeParameter(\"sheetName\", 0) || \"Sheet1\";\n\nconst { google } = require(\"googleapis\");\nconst sheets = google.sheets({ version: \"v4\", auth });\n\nconst response = await sheets.spreadsheets.values.get({\n  spreadsheetId: sheetId,\n  range: sheetName,\n});\n\nreturn response.data.values.map(row => ({ json: { row } }));"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "=https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "2e8681fb-5313-4c55-bbd2-1ce6e27a64ad",
  "connections": {
    "Get many messages": {
      "main": [
        [
          {
            "node": "Extracting Preheader From HTML",
            "type": "main",
            "index": 0
          },
          {
            "node": "Combine HTML, Expected Content and Preheader",
            "type": "main",
            "index": 1
          },
          {
            "node": "Load Expected Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Expected Content": {
      "main": [
        [
          {
            "node": "Combine HTML, Expected Content and Preheader",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extracting Preheader From HTML": {
      "main": [
        [
          {
            "node": "Combine HTML, Expected Content and Preheader",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Extract Actual Content and Results": {
      "main": [
        [
          {
            "node": "Log Content Checks to Excel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Get many messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine HTML, Expected Content and Preheader": {
      "main": [
        [
          {
            "node": "Extract Actual Content and Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}