{
  "id": "xrHo5hsgitvDHCh0",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Team Standup Collector",
  "tags": [],
  "nodes": [
    {
      "id": "1ba5303a-2258-4a84-a16a-4883350606f7",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1392,
        -400
      ],
      "parameters": {
        "width": 768,
        "height": 400,
        "content": "# Team Standup Collector\n\n## How it works:\nThis workflow collects individual standup updates through a webhook, validates the required fields, cleans the submitted data, and stores each response in Google Sheets. A scheduled trigger then reads the sheet every day at 10 AM, filters only that day\u2019s standups, and checks whether any updates were submitted. If standups exist, AI generates a concise team summary and posts it to Slack. If none were submitted, the workflow sends a fallback message to the same channel.\n\n## Setup steps:\n1. Configure the webhook path for standup submissions\n2. Connect Google Sheets and create columns for Timestamp, Name, Yesterday, Today, and Blockers\n3. Connect Gemini credentials for summary generation\n4. Connect Slack and select the target channel\n5. Set the schedule trigger time for daily summary posting\n6. Test webhook submission, sheet logging, and Slack delivery"
      },
      "typeVersion": 1
    },
    {
      "id": "29ac42f7-5595-45ce-9f65-0a6bc0b722af",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 1168,
        "height": 496,
        "content": "## Standup Submission & Validation\nReceives standup updates through a webhook, validates required fields, cleans the submitted values, appends them to Google Sheets, and returns either a success response or a proper error message."
      },
      "typeVersion": 1
    },
    {
      "id": "c1952b31-7706-4c0a-b096-13025597ef17",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 368,
        "content": "## Daily Standup Retrieval\nRuns on schedule, reads all standup entries from Google Sheets, filters only today\u2019s submissions, and checks whether any messages exist before deciding the next summary or fallback path."
      },
      "typeVersion": 1
    },
    {
      "id": "ba2799d1-f6cc-44da-a5d5-b7495a8ed50a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 368,
        "content": "## AI Summary Generation\nUses the filtered standup entries to generate a concise daily team summary with Gemini, focusing on shared progress, priorities, and blockers in a Slack-friendly format."
      },
      "typeVersion": 1
    },
    {
      "id": "724ea34d-6a1a-4092-acf0-094a71136c0f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 592,
        "content": "## Slack Delivery\nPosts the generated standup summary to Slack when updates exist, or sends a fallback message when no standups were submitted by the scheduled summary time."
      },
      "typeVersion": 1
    },
    {
      "id": "fd227411-8561-472f-9298-b1265b57d088",
      "name": "Receive Standup Submission (Webhook)",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -448,
        16
      ],
      "parameters": {
        "path": "team-standup",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "8140ffef-a8c2-4387-b889-3812b814b466",
      "name": "Validate Required Standup Fields",
      "type": "n8n-nodes-base.if",
      "position": [
        -224,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d4dae4b4-d7ee-4e5e-86db-62e904fd752f",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.name }}",
              "rightValue": ""
            },
            {
              "id": "abcac1a7-a20c-4d80-a488-d1f3ca57d33c",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.yesterday }}",
              "rightValue": ""
            },
            {
              "id": "75fe0a0e-90e9-43b7-8f79-348e670a24b2",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.today }}",
              "rightValue": ""
            },
            {
              "id": "df6650e9-ef74-4a07-8d37-aa82265f8510",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.blockers }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "3af6f4a5-9005-4ecb-9470-23881642d2b0",
      "name": "Clean & Format Standup Data",
      "type": "n8n-nodes-base.set",
      "position": [
        0,
        -80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4d6349f8-761a-48f1-b1e6-7c320d896b51",
              "name": "name",
              "type": "string",
              "value": "={{ $json.body.name.trim() }}"
            },
            {
              "id": "a6a4d060-6877-4a42-a15a-e2a61d73e68a",
              "name": "yesterday",
              "type": "string",
              "value": "={{ $json.body.yesterday.trim() }}"
            },
            {
              "id": "67f1981c-252a-474d-a3bb-89966e49dd61",
              "name": "today",
              "type": "string",
              "value": "={{ $json.body.today.trim() }}"
            },
            {
              "id": "ec72f934-5402-4090-b97c-1548a619ebd8",
              "name": "blockers",
              "type": "string",
              "value": "={{ $json.body.blockers.trim() }}"
            },
            {
              "id": "b7599451-ce20-4cc2-be5e-0e4552d8b174",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $now.toFormat('yyyy-LL-dd HH:mm:ss') }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cd3251aa-6781-4eec-ab10-1f9e972923cc",
      "name": "Save Standup to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        224,
        -80
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $json.name }}",
            "Today": "={{ $json.today }}",
            "Blockers": "={{ $json.blockers }}",
            "Timestamp": "={{ $json.timestamp }}",
            "Yesterday": "={{ $json.yesterday }}"
          },
          "schema": [
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Yesterday",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Yesterday",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Today",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Today",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Blockers",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Blockers",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU/edit?usp=drivesdk",
          "cachedResultName": "Standups"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "85e80208-8ebd-48b0-91f1-58101064b3ee",
      "name": "Send Success Response (201)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        448,
        -80
      ],
      "parameters": {
        "options": {
          "responseCode": 201
        },
        "respondWith": "json",
        "responseBody": "{\n  \"message\": \"Standup recorded successfully\"\n}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "bc46af2c-5436-4aac-b366-9e74d5fb303b",
      "name": "Send Error Response (400)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        0,
        112
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "{\n  \"error\": \"Missing required fields: name, yesterday, today, blockers\"\n}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "43b44372-4929-4fac-8fbb-e17d696f226e",
      "name": "Trigger Daily Standup Summary (10 AM)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -432,
        624
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "4e0de807-f8b5-4f08-bf08-0625bc6c0f6d",
      "name": "Fetch Standups from Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -208,
        624
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-bWQixJcxWwL7sRCE3opgpr9jrZz0KdtwfuFHexcyDU/edit?usp=drivesdk",
          "cachedResultName": "Standups"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "85e661f2-2988-4f16-bbb2-3363e395d15c",
      "name": "Filter Today\u2019s Standups",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        624
      ],
      "parameters": {
        "jsCode": "const today = $now.toFormat('d LLL yyyy');\n\nconst filtered = $input.all().filter(item => {\n  const ts = String(item.json.Timestamp || '').trim();\n  return ts.startsWith(today);\n});\n\nreturn [\n  {\n    json: {\n      standups: filtered,\n      count: filtered.length\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "cb6ce83e-2df2-49a4-9248-7e7468994fa0",
      "name": "Check If Standups Exist Today",
      "type": "n8n-nodes-base.if",
      "position": [
        240,
        624
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "9696589b-80ec-4d14-9eff-13b4eb829d97",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.count }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "4fde4156-6c08-4b54-ad5c-52b920fcdf51",
      "name": "Generate Team Standup Summary (AI)",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        592,
        496
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-flash-lite-latest",
          "cachedResultName": "models/gemini-flash-lite-latest"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are creating a concise daily engineering standup summary for Slack.\n\nUsing the submitted standups below, write a short team summary.\n\nRules:\n- Start with: *Daily Standup Summary - {{$now.toFormat(\"dd LLL yyyy\")}}*\n- Write 3-5 concise bullet points\n- Summarize the main progress themes across submitted standups\n- Summarize key priorities for today\n- Mention shared blockers or risks, if any\n- Refer only to submitted standups and do not assume the whole team has submitted\n- Do not list each person individually\n- Do not invent details\n- Keep it brief and Slack-friendly\n- Use *text* for bold (NOT **text**)\n- Use bullets like \u2022 or -\n- Keep it clean and readable in Slack\n\nStandups:\n{{ $json.standups.map(item => \n`Name: ${item.json.Name}\nYesterday: ${item.json.Yesterday}\nToday: ${item.json.Today}\nBlockers: ${item.json.Blockers}`).join('\\n\\n') }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.1
    },
    {
      "id": "5804ac21-651d-49d4-b158-9b16021f1900",
      "name": "Send Standup Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1072,
        416
      ],
      "parameters": {
        "text": "={{ $json.content.parts[0].text }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AR77H22G6",
          "cachedResultName": "daily-standup"
        },
        "otherOptions": {
          "mrkdwn": false,
          "unfurl_links": false,
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "97ba6ba1-193d-4aaf-a0d3-12090b8a413a",
      "name": "Send No-Standup Notification to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1072,
        640
      ],
      "parameters": {
        "text": "=*Daily Standup Summary - {{$now.toFormat(\"dd LLL yyyy\")}}*\n- No standups were submitted by 10:00 AM today.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AR77H22G6",
          "cachedResultName": "daily-standup"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "de17e0ca-6f6a-4ace-b84f-e66259428ef6",
  "connections": {
    "Filter Today\u2019s Standups": {
      "main": [
        [
          {
            "node": "Check If Standups Exist Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean & Format Standup Data": {
      "main": [
        [
          {
            "node": "Save Standup to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Standups Exist Today": {
      "main": [
        [
          {
            "node": "Generate Team Standup Summary (AI)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send No-Standup Notification to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Standup to Google Sheets": {
      "main": [
        [
          {
            "node": "Send Success Response (201)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Required Standup Fields": {
      "main": [
        [
          {
            "node": "Clean & Format Standup Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Error Response (400)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Standups from Google Sheets": {
      "main": [
        [
          {
            "node": "Filter Today\u2019s Standups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Team Standup Summary (AI)": {
      "main": [
        [
          {
            "node": "Send Standup Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Standup Submission (Webhook)": {
      "main": [
        [
          {
            "node": "Validate Required Standup Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger Daily Standup Summary (10 AM)": {
      "main": [
        [
          {
            "node": "Fetch Standups from Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}