This workflow corresponds to n8n.io template #15333 — we link there as the canonical source.
This workflow follows the Gmail → Googlegemini 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": "f68GRq87DoenafLK",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Commodity Portfolio Allocation Tracker with Rebalancing Alerts",
"tags": [],
"nodes": [
{
"id": "7292b81c-f3fb-4b68-abf6-f3a66e703167",
"name": "Overview Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3424,
3728
],
"parameters": {
"width": 1102,
"height": 316,
"content": "## Commodity Portfolio Tracker\n\n**How it works** \nThis workflow runs on a schedule, reads your holdings from Google Sheets, loads target allocation rules from the workflow config node, validates the data, calculates actual allocation, detects drift against your allowed range, classifies the alert severity, generates a plain-text rebalance message with Gemini, emails the result and logs the workflow outcome.\n\n**Setup steps** \n1. Connect Google Sheets, Gmail and Gemini credentials. \n2. Confirm the holdings sheet and spreadsheet are correct. \n3. Create a `rebalance_log` sheet with log columns. \n4. Update target allocation, alert email and thresholds in the config node. \n5. Test the workflow once manually, then activate the schedule."
},
"typeVersion": 1
},
{
"id": "3a83644b-da4f-4f5c-b6be-d51da74b4af3",
"name": "Input and Settings Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3424,
4112
],
"parameters": {
"color": 7,
"width": 932,
"height": 424,
"content": "## Input and Settings\n\nThis section pulls the latest holdings from Google Sheets and loads the portfolio rules used in the workflow. The settings node acts like the control panel, where you can update target allocation, allowed ranges, alert email, currency and severity rules without changing the rest of the flow."
},
"typeVersion": 1
},
{
"id": "233431cb-acd0-41bb-8fe6-9410238a2bf6",
"name": "Analysis and Decision Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4416,
4096
],
"parameters": {
"color": 7,
"width": 1076,
"height": 584,
"content": "## Analysis and Decision\n\nThis part checks whether the input data is clean and usable before doing any calculations. It then works out the actual allocation for each asset, compares it with the target range, identifies which assets are overweight or underweight and decides whether a rebalance alert is needed."
},
"typeVersion": 1
},
{
"id": "8527d92a-918b-4e5c-af4f-86b0f81508c6",
"name": "Alert Message and Logging Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
5552,
3904
],
"parameters": {
"color": 7,
"width": 1304,
"height": 616,
"content": "## Alert Message and Logging\n\nOnce drift is found, this section turns the result into a clear alert message. Gemini helps format the recommendation in simple language, the email node sends it to the selected recipient and the final step stores the outcome in the log sheet so you have a record of what happened in each run."
},
"typeVersion": 1
},
{
"id": "7dd2c230-3740-4a49-a914-026636a254eb",
"name": "Read Holdings",
"type": "n8n-nodes-base.googleSheets",
"position": [
3760,
4384
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA/edit#gid=0",
"cachedResultName": "holdings"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA/edit?usp=drivesdk",
"cachedResultName": "Commodity Portfolio"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "79ce3c3c-442a-4a68-af7d-1a7eac2e6039",
"name": "Workflow Settings",
"type": "n8n-nodes-base.set",
"position": [
3760,
4224
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"targets\": [\n { \"asset\": \"Gold\", \"target_pct\": 40, \"min_pct\": 35, \"max_pct\": 45 },\n { \"asset\": \"Silver\", \"target_pct\": 20, \"min_pct\": 15, \"max_pct\": 25 },\n { \"asset\": \"Oil\", \"target_pct\": 20, \"min_pct\": 15, \"max_pct\": 25 },\n { \"asset\": \"Copper\", \"target_pct\": 10, \"min_pct\": 5, \"max_pct\": 15 },\n { \"asset\": \"Natural Gas\", \"target_pct\": 10, \"min_pct\": 5, \"max_pct\": 15 }\n ],\n \"threshold_pct\": 5,\n \"cooldown_days\": 3,\n \"alert_email\": \"user@example.com\",\n \"email_enabled\": \"yes\",\n \"ai_enabled\": \"yes\",\n \"portfolio_name\": \"Commodity Portfolio\",\n \"currency\": \"INR\",\n \"rebalance_mode\": \"amount\",\n \"severity_rules\": {\n \"low_max\": 5,\n \"medium_max\": 10\n }\n}"
},
"typeVersion": 3.4
},
{
"id": "7d538dfb-8097-4512-b38a-80bd479d671a",
"name": "Synchronize Inputs",
"type": "n8n-nodes-base.merge",
"position": [
4048,
4352
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "c0746678-d2e6-4459-8365-9c20c86a7373",
"name": "Prepare Portfolio Context",
"type": "n8n-nodes-base.code",
"position": [
4240,
4352
],
"parameters": {
"jsCode": "const holdings = $('Read Holdings').all().map(i => i.json);\nconst config = $('Workflow Settings').first().json;\n\nreturn [\n {\n json: {\n holdings,\n config\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "91c40090-c446-45d2-ac33-f9e4c3bec82c",
"name": "Validate Portfolio Data",
"type": "n8n-nodes-base.code",
"position": [
4464,
4352
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst holdings = data.holdings || [];\nconst config = data.config || {};\nconst targets = config.targets || [];\n\nif (!holdings.length) {\n return [{\n json: {\n valid: false,\n error: 'No holdings found.',\n holdings,\n config\n }\n }];\n}\n\nif (!targets.length) {\n return [{\n json: {\n valid: false,\n error: 'Targets are missing in workflow settings.',\n holdings,\n config\n }\n }];\n}\n\nconst targetTotal = targets.reduce((sum, t) => sum + Number(t.target_pct || 0), 0);\nif (targetTotal !== 100) {\n return [{\n json: {\n valid: false,\n error: `Target allocation must total 100. Current total is ${targetTotal}.`,\n holdings,\n config\n }\n }];\n}\n\nconst seenAssets = new Set();\n\nfor (const h of holdings) {\n const asset = String(h.asset || '').trim();\n const units = Number(h.units);\n const price = Number(h.price);\n\n if (!asset) {\n return [{\n json: {\n valid: false,\n error: 'One holding has a blank asset name.',\n holdings,\n config\n }\n }];\n }\n\n if (seenAssets.has(asset)) {\n return [{\n json: {\n valid: false,\n error: `Duplicate asset found in holdings: ${asset}`,\n holdings,\n config\n }\n }];\n }\n seenAssets.add(asset);\n\n if (Number.isNaN(units) || units < 0) {\n return [{\n json: {\n valid: false,\n error: `Invalid units for asset: ${asset}`,\n holdings,\n config\n }\n }];\n }\n\n if (Number.isNaN(price) || price < 0) {\n return [{\n json: {\n valid: false,\n error: `Invalid price for asset: ${asset}`,\n holdings,\n config\n }\n }];\n }\n\n const target = targets.find(t => String(t.asset || '').trim() === asset);\n if (!target) {\n return [{\n json: {\n valid: false,\n error: `No target config found for asset: ${asset}`,\n holdings,\n config\n }\n }];\n }\n}\n\nreturn [{\n json: {\n valid: true,\n error: null,\n holdings,\n config\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f66778ac-7cb4-4ce7-b4cc-81e08796c440",
"name": "Check Validation Status",
"type": "n8n-nodes-base.if",
"position": [
4704,
4352
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.valid }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "52d2ef3f-d231-49b2-b7b2-e134326fe6a4",
"name": "Calculate Portfolio Allocation",
"type": "n8n-nodes-base.code",
"position": [
4928,
4240
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst holdings = data.holdings;\nconst config = data.config;\n\nconst enrichedHoldings = holdings.map(h => {\n const units = Number(h.units);\n const price = Number(h.price);\n const current_value = Number(h.current_value ?? (units * price));\n\n return {\n row_number: h.row_number,\n asset: h.asset,\n units,\n price,\n current_value,\n last_updated: h.last_updated\n };\n});\n\nconst total_value = enrichedHoldings.reduce((sum, h) => sum + h.current_value, 0);\n\nconst assets = enrichedHoldings.map(h => {\n const target = config.targets.find(t => t.asset === h.asset);\n\n if (!target) {\n throw new Error(`Missing target config for asset: ${h.asset}`);\n }\n\n const actual_pct = total_value === 0 ? 0 : (h.current_value / total_value) * 100;\n\n return {\n ...h,\n actual_pct,\n target_pct: Number(target.target_pct),\n min_pct: Number(target.min_pct),\n max_pct: Number(target.max_pct)\n };\n});\n\nreturn [{\n json: {\n holdings: assets,\n total_value,\n config\n }\n}];"
},
"typeVersion": 2
},
{
"id": "15978e61-2607-442d-9004-63d5aa829bb8",
"name": "Detect Portfolio Drift",
"type": "n8n-nodes-base.code",
"position": [
5136,
4240
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst holdings = data.holdings;\nconst total_value = data.total_value;\nconst config = data.config;\n\nlet rebalance_needed = false;\n\nconst assets = holdings.map(h => {\n const deviation_pct = h.actual_pct - h.target_pct;\n const target_value = total_value * (h.target_pct / 100);\n const adjustment_amount = Math.abs(h.current_value - target_value);\n\n let status = 'within_range';\n let suggested_action = 'Hold';\n\n if (h.actual_pct > h.max_pct) {\n status = 'overweight';\n suggested_action = 'Reduce';\n rebalance_needed = true;\n } else if (h.actual_pct < h.min_pct) {\n status = 'underweight';\n suggested_action = 'Increase';\n rebalance_needed = true;\n }\n\n return {\n ...h,\n deviation_pct,\n target_value,\n adjustment_amount,\n status,\n suggested_action\n };\n});\n\nconst affected_assets = assets\n .filter(a => a.status !== 'within_range')\n .map(a => a.asset);\n\nreturn [{\n json: {\n total_value,\n assets,\n affected_assets,\n rebalance_needed,\n config\n }\n}];"
},
"typeVersion": 2
},
{
"id": "3d0e7dce-1d90-44c7-8751-5c83c27b46bb",
"name": "Check Rebalance Requirement",
"type": "n8n-nodes-base.if",
"position": [
5360,
4240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.rebalance_needed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "ec658cb8-383f-43c4-8c95-3717f711a94e",
"name": "Classify Alert Severity",
"type": "n8n-nodes-base.code",
"position": [
5584,
4144
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst assets = data.assets;\nconst config = data.config;\n\nconst maxDeviation = Math.max(...assets.map(a => Math.abs(a.deviation_pct)));\n\nlet severity = 'low';\nif (maxDeviation > Number(config.severity_rules.medium_max)) {\n severity = 'high';\n} else if (maxDeviation > Number(config.severity_rules.low_max)) {\n severity = 'medium';\n}\n\nreturn [{\n json: {\n ...data,\n severity,\n max_deviation: maxDeviation\n }\n}];"
},
"typeVersion": 2
},
{
"id": "622f0d6d-17b4-4ec0-ab5f-1f67ddd90b95",
"name": "Build Alert Prompt",
"type": "n8n-nodes-base.code",
"position": [
5808,
4144
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\nconst affected = data.assets\n .filter(a => a.status !== 'within_range')\n .map(a => `${a.asset}: ${a.status}, actual ${a.actual_pct.toFixed(2)}%, target ${a.target_pct.toFixed(2)}%, deviation ${a.deviation_pct.toFixed(2)}%, action ${a.suggested_action}, amount ${data.config.currency} ${a.adjustment_amount.toFixed(2)}`)\n .join('\\n');\n\nconst ai_input = `\nYou are a financial portfolio monitoring assistant.\n\nYour task is to convert structured portfolio rebalance data into a professional alert message.\n\nSTRICT RULES:\n- Use only the numbers provided below\n- Do NOT recalculate anything\n- Do NOT invent any values\n- Keep the message concise, clear and professional\n- Use plain text only\n- Mention severity naturally\n\nDATA:\nPortfolio Name: ${data.config.portfolio_name}\nCurrency: ${data.config.currency}\nTotal Portfolio Value: ${data.total_value}\nSeverity: ${data.severity}\nMaximum Deviation: ${data.max_deviation.toFixed(2)}%\n\nAssets requiring rebalance:\n${affected}\n\nOUTPUT FORMAT:\nWrite a clean paragraph-style alert message suitable for email.\n`;\n\nreturn [{\n json: {\n ...data,\n ai_input\n }\n}];"
},
"typeVersion": 2
},
{
"id": "9379e3af-ce16-481c-9def-ca9541d41667",
"name": "Generate Alert Message",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
5952,
4064
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-3.1-flash-lite-preview",
"cachedResultName": "models/gemini-3.1-flash-lite-preview"
},
"options": {},
"messages": {
"values": [
{
"content": "={{ $json.ai_input }}"
}
]
},
"builtInTools": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "77a77ce0-4569-4c61-bb8c-cf7a1d885dcd",
"name": "Merge Alert Data",
"type": "n8n-nodes-base.merge",
"position": [
6256,
4128
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "a8d39754-f8b5-4cab-a8f2-819ceec55b46",
"name": "Prepare Alert Payload",
"type": "n8n-nodes-base.set",
"position": [
6432,
4128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3de685c8-ff1f-43a1-870d-e58542b7596b",
"name": "alert_message",
"type": "string",
"value": "={{ $json.content.parts[0].text }}"
},
{
"id": "fbc8f85a-24b4-40d0-9981-e4b85f38c6a1",
"name": "email_to",
"type": "string",
"value": "={{ $json.config.alert_email }}"
},
{
"id": "dc6b09bd-6745-4110-8ec4-2322014da2a1",
"name": "email_subject",
"type": "string",
"value": "=Commodity Portfolio Alert - {{$json.severity.toUpperCase()}}"
},
{
"id": "c715d84c-f0c7-47c5-a3ef-2dd0c2bccbd6",
"name": "affected_assets_text",
"type": "string",
"value": "={{ $json.affected_assets.join(', ') }}"
},
{
"id": "f4861430-940f-48b2-be93-2d810c5ada97",
"name": "run_date",
"type": "string",
"value": "={{ $now }}"
},
{
"id": "aef321d5-2581-43e2-85db-42cdb7515f16",
"name": "alert_sent",
"type": "string",
"value": "yes"
},
{
"id": "43b07038-8485-4efa-9c06-7d0eee590f5e",
"name": "total_value",
"type": "number",
"value": "={{ $json.total_value }}"
},
{
"id": "24388c93-02d0-4fa8-b84f-83a7f1fca40b",
"name": "severity",
"type": "string",
"value": "={{ $json.severity }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "708ee600-4e9a-4265-bbf1-3c105dbf080f",
"name": "Send Rebalance Email",
"type": "n8n-nodes-base.gmail",
"position": [
6608,
4048
],
"parameters": {
"sendTo": "={{ $('Prepare Alert Payload').item.json.email_to }}",
"message": "={{ $('Prepare Alert Payload').item.json.alert_message + '\\n\\nAffected Assets: ' + $('Prepare Alert Payload').item.json.affected_assets_text + '\\nSeverity: ' + $('Prepare Alert Payload').item.json.severity + '\\nTotal Portfolio Value: ' + $('Prepare Alert Payload').item.json.total_value + '\\nRun Date: ' + $('Prepare Alert Payload').item.json.run_date }}",
"options": {},
"subject": "={{ $('Prepare Alert Payload').item.json.email_subject }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "f6e4ac7b-b17d-4d3e-91f4-4656880e1a4a",
"name": "Log Sent Alert",
"type": "n8n-nodes-base.googleSheets",
"position": [
6608,
4224
],
"parameters": {
"columns": {
"value": {
"summary": "={{ $json.alert_message }}",
"run_date": "={{ $json.run_date }}",
"severity": "={{ $json.severity }}",
"alert_sent": "={{ $json.alert_sent }}",
"total_value": "={{ $json.total_value }}",
"affected_assets": "={{ $json.affected_assets_text }}",
"rebalance_needed": "yes"
},
"schema": [
{
"id": "run_date",
"type": "string",
"display": true,
"required": false,
"displayName": "run_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_value",
"type": "string",
"display": true,
"required": false,
"displayName": "total_value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rebalance_needed",
"type": "string",
"display": true,
"required": false,
"displayName": "rebalance_needed",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "severity",
"type": "string",
"display": true,
"required": false,
"displayName": "severity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "affected_assets",
"type": "string",
"display": true,
"required": false,
"displayName": "affected_assets",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alert_sent",
"type": "string",
"display": true,
"required": false,
"displayName": "alert_sent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "summary",
"type": "string",
"display": true,
"required": false,
"displayName": "summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reason_skipped",
"type": "string",
"display": true,
"required": false,
"displayName": "reason_skipped",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 347522276,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA/edit#gid=347522276",
"cachedResultName": "rebalance_log"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA",
"cachedResultName": "Commodity Portfolio"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "91883dee-8be2-4ed6-93a8-802dda291517",
"name": "Prepare Validation Failure Log",
"type": "n8n-nodes-base.set",
"position": [
4928,
4512
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "run_date",
"type": "string",
"value": "={{ $now }}"
},
{
"name": "total_value",
"type": "string",
"value": ""
},
{
"name": "rebalance_needed",
"type": "string",
"value": "no"
},
{
"name": "severity",
"type": "string",
"value": "validation_failed"
},
{
"name": "affected_assets",
"type": "string",
"value": ""
},
{
"name": "alert_sent",
"type": "string",
"value": "no"
},
{
"name": "summary",
"type": "string",
"value": "Validation failed before allocation analysis."
},
{
"name": "reason_skipped",
"type": "string",
"value": "={{ $json.error }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5300f585-5b9a-42c8-88ca-b3cb8766ac80",
"name": "Log Validation Failure",
"type": "n8n-nodes-base.googleSheets",
"position": [
5136,
4512
],
"parameters": {
"columns": {
"value": {
"summary": "={{ $json.summary }}",
"run_date": "={{ $json.run_date }}",
"severity": "={{ $json.severity }}",
"alert_sent": "={{ $json.alert_sent }}",
"total_value": "={{ $json.total_value }}",
"reason_skipped": "={{ $json.reason_skipped }}",
"affected_assets": "={{ $json.affected_assets }}",
"rebalance_needed": "={{ $json.rebalance_needed }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 347522276,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA/edit#gid=347522276",
"cachedResultName": "rebalance_log"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA",
"cachedResultName": "Commodity Portfolio"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "c899fa73-de9a-4814-ab75-71489c8da0d2",
"name": "Prepare No Action Log",
"type": "n8n-nodes-base.set",
"position": [
5584,
4304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "run_date",
"type": "string",
"value": "={{ $now }}"
},
{
"name": "total_value",
"type": "string",
"value": "={{ $json.total_value }}"
},
{
"name": "rebalance_needed",
"type": "string",
"value": "no"
},
{
"name": "severity",
"type": "string",
"value": "none"
},
{
"name": "affected_assets",
"type": "string",
"value": ""
},
{
"name": "alert_sent",
"type": "string",
"value": "no"
},
{
"name": "summary",
"type": "string",
"value": "No drift detected. Portfolio is within target range."
},
{
"name": "reason_skipped",
"type": "string",
"value": "No rebalance needed."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e62048b2-5b87-4166-84cb-25109dcdfdfd",
"name": "Log No Action",
"type": "n8n-nodes-base.googleSheets",
"position": [
5808,
4304
],
"parameters": {
"columns": {
"value": {
"summary": "={{ $json.summary }}",
"run_date": "={{ $json.run_date }}",
"severity": "={{ $json.severity }}",
"alert_sent": "={{ $json.alert_sent }}",
"total_value": "={{ $json.total_value }}",
"reason_skipped": "={{ $json.reason_skipped }}",
"affected_assets": "={{ $json.affected_assets }}",
"rebalance_needed": "={{ $json.rebalance_needed }}"
},
"schema": [
{
"id": "run_date",
"type": "string",
"display": true,
"required": false,
"displayName": "run_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_value",
"type": "string",
"display": true,
"required": false,
"displayName": "total_value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rebalance_needed",
"type": "string",
"display": true,
"required": false,
"displayName": "rebalance_needed",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "severity",
"type": "string",
"display": true,
"required": false,
"displayName": "severity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "affected_assets",
"type": "string",
"display": true,
"required": false,
"displayName": "affected_assets",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alert_sent",
"type": "string",
"display": true,
"required": false,
"displayName": "alert_sent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "summary",
"type": "string",
"display": true,
"required": false,
"displayName": "summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reason_skipped",
"type": "string",
"display": true,
"required": false,
"displayName": "reason_skipped",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 347522276,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA/edit#gid=347522276",
"cachedResultName": "rebalance_log"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_lcrJ7b0c9ZqyenXZZZ-A5Yyb99lS-k8qSs-Y9PV3EA",
"cachedResultName": "Commodity Portfolio"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "0695d34f-1ed2-4f48-8d5b-07fd7ae07158",
"name": "Daily Portfolio Rebalance Check",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3488,
4352
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.3
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "8bcf8ba9-03c0-47a1-b028-912e88818797",
"connections": {
"Read Holdings": {
"main": [
[
{
"node": "Synchronize Inputs",
"type": "main",
"index": 1
}
]
]
},
"Log Sent Alert": {
"main": [
[]
]
},
"Merge Alert Data": {
"main": [
[
{
"node": "Prepare Alert Payload",
"type": "main",
"index": 0
}
]
]
},
"Workflow Settings": {
"main": [
[
{
"node": "Synchronize Inputs",
"type": "main",
"index": 0
}
]
]
},
"Build Alert Prompt": {
"main": [
[
{
"node": "Generate Alert Message",
"type": "main",
"index": 0
},
{
"node": "Merge Alert Data",
"type": "main",
"index": 1
}
]
]
},
"Synchronize Inputs": {
"main": [
[
{
"node": "Prepare Portfolio Context",
"type": "main",
"index": 0
}
]
]
},
"Send Rebalance Email": {
"main": [
[]
]
},
"Prepare Alert Payload": {
"main": [
[
{
"node": "Log Sent Alert",
"type": "main",
"index": 0
},
{
"node": "Send Rebalance Email",
"type": "main",
"index": 0
}
]
]
},
"Prepare No Action Log": {
"main": [
[
{
"node": "Log No Action",
"type": "main",
"index": 0
}
]
]
},
"Detect Portfolio Drift": {
"main": [
[
{
"node": "Check Rebalance Requirement",
"type": "main",
"index": 0
}
]
]
},
"Generate Alert Message": {
"main": [
[
{
"node": "Merge Alert Data",
"type": "main",
"index": 0
}
]
]
},
"Check Validation Status": {
"main": [
[
{
"node": "Calculate Portfolio Allocation",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Validation Failure Log",
"type": "main",
"index": 0
}
]
]
},
"Classify Alert Severity": {
"main": [
[
{
"node": "Build Alert Prompt",
"type": "main",
"index": 0
}
]
]
},
"Validate Portfolio Data": {
"main": [
[
{
"node": "Check Validation Status",
"type": "main",
"index": 0
}
]
]
},
"Prepare Portfolio Context": {
"main": [
[
{
"node": "Validate Portfolio Data",
"type": "main",
"index": 0
}
]
]
},
"Check Rebalance Requirement": {
"main": [
[
{
"node": "Classify Alert Severity",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare No Action Log",
"type": "main",
"index": 0
}
]
]
},
"Calculate Portfolio Allocation": {
"main": [
[
{
"node": "Detect Portfolio Drift",
"type": "main",
"index": 0
}
]
]
},
"Prepare Validation Failure Log": {
"main": [
[
{
"node": "Log Validation Failure",
"type": "main",
"index": 0
}
]
]
},
"Daily Portfolio Rebalance Check": {
"main": [
[
{
"node": "Read Holdings",
"type": "main",
"index": 0
},
{
"node": "Workflow Settings",
"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.
gmailOAuth2googlePalmApigoogleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically monitors a commodity portfolio stored in Google Sheets, compares actual allocation against predefined targets, detects deviations and sends intelligent rebalance alerts via email using Gemini AI. It also logs every run (success, failure or no action)…
Source: https://n8n.io/workflows/15333/ — 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 is a complete outbound automation system that discovers local businesses, extracts contact emails, generates personalized cold emails using AI, and runs a multi-step follow-up sequence —
This workflow automatically fetches the latest business news, analyzes its impact on clients using AI and sends alerts for high-impact articles while logging all processed data in Google Sheets. It en
> n8n, NSE RSS, Google Sheets, Gemini AI and Gmail
This workflow automatically fetches daily stock market news, analyzes sentiment using Gemini AI, calculates impact scores, sends alerts for high-impact news and stores structured results in Google She
This workflow automatically generates a weekly financial advisory briefing every Monday at 8 AM. It fetches live market data (SPY), collects top financial news, uses Google Gemini AI to generate clien