AutomationFlowsAI & RAG › Cma Automation

Cma Automation

CMA Automation. Uses googleDriveTrigger, googleDrive, informationExtractor, lmChatGoogleGemini. Event-driven trigger; 11 nodes.

Event trigger★★★★☆ complexityAI-powered11 nodesGoogle Drive TriggerGoogle DriveInformation ExtractorGoogle Gemini ChatN8N Nodes PdfmonkeyHTTP Request
AI & RAG Trigger: Event Nodes: 11 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Google Drive → Google Drive Trigger 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
{
  "updatedAt": "2025-11-23T18:52:50.000Z",
  "createdAt": "2025-11-21T18:29:25.700Z",
  "id": "FsE5BMhYYzTYRTwM",
  "name": "CMA Automation",
  "active": false,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "value": "1CNXRwP4cKQTRwfA4ytZsb6TenJR_mY0K",
          "mode": "list",
          "cachedResultName": "Quick CMA",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1CNXRwP4cKQTRwfA4ytZsb6TenJR_mY0K"
        },
        "event": "fileCreated",
        "options": {}
      },
      "type": "n8n-nodes-base.googleDriveTrigger",
      "typeVersion": 1,
      "position": [
        464,
        504
      ],
      "id": "25630b52-ebb1-4935-a3f8-578b63ebe423",
      "name": "Google Drive Trigger",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "pdf",
        "options": {}
      },
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        912,
        504
      ],
      "id": "efe97e3b-b059-4c23-bf3d-11bd10c81eb1",
      "name": "Extract from File"
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        688,
        504
      ],
      "id": "6080b227-93f6-4feb-9aed-c1737c49a51f",
      "name": "Download file",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "text": "={{ $json.text }}",
        "attributes": {
          "attributes": [
            {
              "name": "Address",
              "description": "Address of the comparable property",
              "required": true
            },
            {
              "name": "H SqFt",
              "description": "Square feet of the comparable property.",
              "required": true
            },
            {
              "name": "BR",
              "description": "How many bedrooms the comparable property has.",
              "required": true
            },
            {
              "name": "FB",
              "description": "How many full bathrooms the comparable property has",
              "required": true
            },
            {
              "name": "HB",
              "description": "How many half bathrooms the comparable property has.",
              "required": true
            },
            {
              "name": "YrBlt",
              "description": "The year the property was built",
              "required": true
            },
            {
              "name": "Pool",
              "description": "If it has a pool, the type of pool",
              "required": true
            },
            {
              "name": "List Price",
              "description": "The price the property was listed for",
              "required": true
            },
            {
              "name": "CDOM",
              "description": "The amount of days on the market the property was listed.",
              "required": true
            },
            {
              "name": "Sale Price",
              "description": "The price of the property was sold at.",
              "required": true
            },
            {
              "name": "Sale Date",
              "description": "The date the property was sold.",
              "required": true
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        464,
        936
      ],
      "id": "0d96f76d-eb14-4211-90fc-e0b727d0a025",
      "name": "Information Extractor"
    },
    {
      "parameters": {
        "content": "### Sale $ / foot\n**Means sale $ / foot\nSale Price / H SqFt = P/H SqFt\n\n### List $ /foot\n** Means \n(listing price / H sqft)",
        "height": 192,
        "width": 272
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        48
      ],
      "typeVersion": 1,
      "id": "2eb847a4-5939-4d3b-8006-fea4e81aaec4",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        536,
        1160
      ],
      "id": "1b92fd80-f76d-405b-a9fe-9ec60531ce6e",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        1208,
        728
      ],
      "id": "e98c4649-dcf3-4719-ba4b-cbc8f021dbf8",
      "name": "Google Gemini Chat Model1",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "text": "={{ $json.text }}",
        "schemaType": "fromJson",
        "jsonSchemaExample": "[\n  {\n    \"Active Listings\": [\n      {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Active\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Active\"\n    }\n    ],\n    \"Pending Listings\": [\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Pending\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Pending\"\n    }\n    ],\n    \"Sold Listings\": [\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Sold\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Sold\"\n    }\n    ]\n  }\n]",
        "options": {
          "systemPromptTemplate": "You are an expert extraction algorithm.\nOnly extract relevant information from the text.\nIf you do not know the value of an attribute asked to extract, you may omit the attribute's value. "
        }
      },
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        1136,
        504
      ],
      "id": "81f3a9a6-d6c6-4e69-8e83-3deb5209e413",
      "name": "Information Extractor1"
    },
    {
      "parameters": {
        "jsCode": "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst pptxgen = require(\"pptxgenjs\");\nconst { html2pptx } = require(\"./html2pptx\");\n\n// Read JSON data\nconst jsonPath = process.argv[2] || \"/mnt/user-data/uploads/cma_data.json\";\nconst outputPath = process.argv[3] || \"CMA_Report.pptx\";\n\nconst rawData = JSON.parse(fs.readFileSync(jsonPath, \"utf8\"));\nconst data = rawData[0].output[0];\n\n// Helper function to parse price string to number\nfunction parsePrice(priceStr) {\n  if (!priceStr) return 0;\n  return parseFloat(priceStr.replace(/[$,]/g, \"\")) || 0;\n}\n\n// Helper function to parse sqft string to number\nfunction parseSqft(sqftStr) {\n  if (!sqftStr) return 0;\n  return parseFloat(sqftStr.replace(/,/g, \"\")) || 0;\n}\n\n// Calculate price per sqft\nfunction calcPricePerSqft(price, sqft) {\n  const p = parsePrice(price);\n  const s = parseSqft(sqft);\n  if (s === 0) return \"$0\";\n  return \"$\" + Math.round(p / s).toLocaleString();\n}\n\n// Format baths (FB + HB)\nfunction formatBaths(fb, hb) {\n  const full = parseInt(fb) || 0;\n  const half = parseInt(hb) || 0;\n  if (half > 0) return `${full}.${half}`;\n  return `${full}`;\n}\n\n// Generate title slide HTML\nfunction generateTitleSlide() {\n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px; position: relative;\">\n  <!-- Header bar -->\n  <div style=\"width: 100%; height: 80px; background: #2c3e5c;\" class=\"fit\"></div>\n  <div style=\"width: 100%; height: 12px; background: #808080;\" class=\"fit\"></div>\n  \n  <!-- Main content -->\n  <div class=\"fill-height col center\" style=\"padding: 20px;\">\n    <h1 style=\"font-size: 42px; color: #2c3e5c; margin: 0 0 30px 0; text-align: center;\">Comparative Market Analysis</h1>\n    <p style=\"font-size: 24px; color: #555; margin: 0 0 40px 0; text-align: center;\">Prepared for Your Property</p>\n    \n    <div style=\"display: flex; gap: 60px; margin-top: 20px;\">\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Active Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Active Listings\"].length}</p>\n      </div>\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Pending Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Pending Listings\"].length}</p>\n      </div>\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Sold Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Sold Listings\"].length}</p>\n      </div>\n    </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Generate listing card HTML\nfunction generateListingCard(listing, isSold = false) {\n  const pricePerSqft = calcPricePerSqft(listing[\"List Price\"], listing[\"H SqFt\"]);\n  const baths = formatBaths(listing[\"FB\"], listing[\"HB\"]);\n  \n  let soldInfo = \"\";\n  if (isSold && listing[\"Sale Price\"]) {\n    const salePerSqft = calcPricePerSqft(listing[\"Sale Price\"], listing[\"H SqFt\"]);\n    soldInfo = `\n      <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Sale Price:</b> ${listing[\"Sale Price\"]}</p>\n      <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Sale $/Ft:</b> ${salePerSqft}</p>\n      <p style=\"font-size: 10px; margin: 0;\"><b style=\"color: #2c3e5c;\">Sale Date:</b> ${listing[\"Sale Date\"]}</p>`;\n  }\n  \n  return `\n    <div style=\"width: 280px; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px;\">\n      <!-- Image placeholder -->\n      <div style=\"width: 280px; height: 100px; background: #f0f0f0; border-radius: 4px 4px 0 0; border-bottom: 1px solid #e0e0e0;\">\n        <p style=\"font-size: 11px; color: #999; text-align: center; margin: 0; padding-top: 40px;\">Insert Property Image</p>\n      </div>\n      \n      <!-- Address header -->\n      <div style=\"background: #2c3e5c; padding: 6px;\">\n        <p style=\"font-size: 11px; font-weight: bold; text-align: center; margin: 0; color: white;\">${listing[\"Address\"]}</p>\n      </div>\n      \n      <!-- Property details -->\n      <div class=\"col\" style=\"padding: 8px;\">\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">SqFt:</b> ${listing[\"H SqFt\"]}  |  <b style=\"color: #2c3e5c;\">BR:</b> ${listing[\"BR\"]}  |  <b style=\"color: #2c3e5c;\">BA:</b> ${baths}</p>\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Year:</b> ${listing[\"YrBlt\"]}  |  <b style=\"color: #2c3e5c;\">Pool:</b> ${listing[\"Pool\"]}</p>\n        \n        <!-- Divider -->\n        <div style=\"height: 1px; background: #e0e0e0; margin: 4px 0;\"></div>\n        \n        <!-- Pricing details -->\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">List Price:</b> ${listing[\"List Price\"]}  |  <b style=\"color: #2c3e5c;\">$/Ft:</b> ${pricePerSqft}</p>\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">D.O.M.:</b> ${listing[\"CDOM\"]}</p>\n        ${soldInfo}\n      </div>\n    </div>`;\n}\n\n// Generate listings page HTML\nfunction generateListingsPage(title, listings, startIdx, count, isSold = false) {\n  const pageListings = listings.slice(startIdx, startIdx + count);\n  const cards = pageListings.map(l => generateListingCard(l, isSold)).join(\"\\n\");\n  \n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px;\">\n  <!-- Wrapper with margins -->\n  <div class=\"col fill-height\" style=\"margin: 24px 24px 20px 24px;\">\n    <!-- Title -->\n    <h1 style=\"font-size: 34px; color: #1d1d1d; margin: 0 0 10px 0; font-weight: bold;\">${title}</h1>\n    \n    <!-- Header bars -->\n    <div class=\"row fit\">\n      <div style=\"flex: 1; height: 8px; background: #2c3e5c;\"></div>\n      <div style=\"flex: 1; height: 8px; background: #808080;\"></div>\n    </div>\n    \n    <!-- Content area with cards -->\n    <div class=\"fill-height row\" style=\"padding-top: 20px; gap: 20px; justify-content: center; align-items: flex-start;\">\n      ${cards}\n    </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Generate totals page HTML\nfunction generateTotalsPage() {\n  // Calculate Active stats\n  const activeListings = data[\"Active Listings\"];\n  const activeQty = activeListings.length;\n  const activeAvgDOM = Math.round(activeListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / activeQty);\n  const activeAvgPrice = Math.round(activeListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / activeQty);\n  const activeAvgPriceSqft = Math.round(activeListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / activeQty);\n  \n  // Calculate Pending stats\n  const pendingListings = data[\"Pending Listings\"];\n  const pendingQty = pendingListings.length;\n  const pendingAvgDOM = Math.round(pendingListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / pendingQty);\n  const pendingAvgPrice = Math.round(pendingListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / pendingQty);\n  const pendingAvgPriceSqft = Math.round(pendingListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / pendingQty);\n  \n  // Calculate Sold stats\n  const soldListings = data[\"Sold Listings\"];\n  const soldQty = soldListings.length;\n  const soldAvgDOM = Math.round(soldListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / soldQty);\n  const soldAvgListPrice = Math.round(soldListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / soldQty);\n  const soldAvgListPriceSqft = Math.round(soldListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / soldQty);\n  const soldAvgSalePrice = Math.round(soldListings.reduce((sum, l) => sum + parsePrice(l[\"Sale Price\"]), 0) / soldQty);\n  const soldAvgSalePriceSqft = Math.round(soldListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"Sale Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / soldQty);\n  \n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px;\">\n  <!-- Wrapper with margins -->\n  <div class=\"col fill-height\" style=\"margin: 24px 24px 20px 24px;\">\n    <!-- Title -->\n    <h1 style=\"font-size: 34px; color: #1d1d1d; margin: 0 0 10px 0; font-weight: bold;\">Totals</h1>\n    \n    <!-- Header bars -->\n    <div class=\"row fit\">\n      <div style=\"flex: 1; height: 8px; background: #2c3e5c;\"></div>\n      <div style=\"flex: 1; height: 8px; background: #808080;\"></div>\n    </div>\n    \n    <!-- Content -->\n    <div class=\"fill-height col\" style=\"padding-top: 20px; gap: 16px;\">\n    \n    <!-- ACTIVE Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">ACTIVE</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${activeQty}  |  <b>Average D.O.M.:</b> ${activeAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${activeAvgPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${activeAvgPriceSqft}</p>\n    </div>\n    \n    <!-- PENDING Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">PENDING</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${pendingQty}  |  <b>Average D.O.M.:</b> ${pendingAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${pendingAvgPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${pendingAvgPriceSqft}</p>\n    </div>\n    \n    <!-- SOLD Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">SOLD</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${soldQty}  |  <b>Average D.O.M.:</b> ${soldAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${soldAvgListPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${soldAvgListPriceSqft}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average Sold Price:</b> $${soldAvgSalePrice.toLocaleString()}  |  <b>Average Sold $/SqFt:</b> $${soldAvgSalePriceSqft}</p>\n    </div>\n    \n  </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Main generation function\nasync function generatePresentation() {\n  const pptx = new pptxgen();\n  pptx.layout = \"LAYOUT_16x9\";\n  pptx.title = \"Comparative Market Analysis\";\n  pptx.author = \"CMA Generator\";\n  \n  // Create HTML files\n  const slides = [];\n  \n  // Slide 1: Title\n  fs.writeFileSync(\"slide-title.html\", generateTitleSlide());\n  slides.push(\"slide-title.html\");\n  \n  // Slide 2: Active Listings (all 3 on one page)\n  fs.writeFileSync(\"slide-active.html\", generateListingsPage(\"Active Listings\", data[\"Active Listings\"], 0, 3, false));\n  slides.push(\"slide-active.html\");\n  \n  // Slide 3: Pending Listings (2 on one page)\n  fs.writeFileSync(\"slide-pending.html\", generateListingsPage(\"Pending Listings\", data[\"Pending Listings\"], 0, 3, false));\n  slides.push(\"slide-pending.html\");\n  \n  // Slide 4-5: Sold Listings (3 per page)\n  fs.writeFileSync(\"slide-sold1.html\", generateListingsPage(\"Sold Listings\", data[\"Sold Listings\"], 0, 3, true));\n  slides.push(\"slide-sold1.html\");\n  \n  fs.writeFileSync(\"slide-sold2.html\", generateListingsPage(\"Sold Listings\", data[\"Sold Listings\"], 3, 3, true));\n  slides.push(\"slide-sold2.html\");\n  \n  // Slide 6: Totals\n  fs.writeFileSync(\"slide-totals.html\", generateTotalsPage());\n  slides.push(\"slide-totals.html\");\n  \n  // Convert all HTML slides to PowerPoint\n  for (const slideFile of slides) {\n    await html2pptx(slideFile, pptx);\n  }\n  \n  // Save the presentation\n  await pptx.writeFile(outputPath);\n  console.log(`Presentation saved to: ${outputPath}`);\n}\n\ngeneratePresentation().catch(console.error);\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1488,
        504
      ],
      "id": "ca8fa22b-bf8e-4a54-a0e0-33703ef20276",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-pdfmonkey.pdfMonkey",
      "typeVersion": 1,
      "position": [
        464,
        176
      ],
      "id": "73125177-5242-4cd3-a2e1-211ab03b807a",
      "name": "PDFMonkey",
      "credentials": {
        "pdfMonkeyApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1840,
        640
      ],
      "id": "8053e5d7-cbef-432e-93e3-be7c808d9111",
      "name": "HTTP Request"
    }
  ],
  "connections": {
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Download file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download file": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Information Extractor1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Information Extractor",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Information Extractor1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Information Extractor1": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "versionId": "b01c4d3c-ebc3-4774-a0e1-b598bc41f8da",
  "activeVersionId": null,
  "triggerCount": 0,
  "shared": [
    {
      "updatedAt": "2025-11-21T18:29:25.702Z",
      "createdAt": "2025-11-21T18:29:25.702Z",
      "role": "workflow:owner",
      "workflowId": "FsE5BMhYYzTYRTwM",
      "projectId": "Jb2DFpuDeQFonpJH"
    }
  ],
  "activeVersion": null,
  "tags": []
}

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

CMA Automation. Uses googleDriveTrigger, googleDrive, informationExtractor, lmChatGoogleGemini. Event-driven trigger; 11 nodes.

Source: https://github.com/gllanes005/n8n-workflows/blob/71a4fc487f239d6f71dcae7109bc3d8124f6de8e/workflows/CMA-Automation.json — 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 n8n template automates scraping content from Skool communities using the Olostep API. It collects structured data from Skool pages and stores it in a clean format, making it easy to analyze commu

N8N Nodes Olostep, Form Trigger, HTTP Request +3
AI & RAG

automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.

Telegram Trigger, Telegram, Google Gemini +4
AI & RAG

automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.

Telegram Trigger, Telegram, Google Gemini +4
AI & RAG

automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 35 nodes.

Telegram Trigger, Telegram, Google Gemini +4
AI & RAG

automation_financial_recording. Uses telegramTrigger, telegram, googleGemini, lmChatGoogleGemini. Event-driven trigger; 29 nodes.

Telegram Trigger, Telegram, Google Gemini +4