{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "\ud83d\udccb Weekly Standup with AI Summary \u2014 Telegram + Google Sheets",
  "nodes": [
    {
      "id": "bcb59745-6c33-420e-a70e-15fef91900f9",
      "name": "\ud83d\udcdd Prepare Messages",
      "type": "n8n-nodes-base.code",
      "position": [
        1056,
        2192
      ],
      "parameters": {
        "jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// PREPARE STANDUP MESSAGES + READ HISTORY\n// One item per team member\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst config = $input.first().json;\nconst chatIds = config.team_chat_ids.split(',').map(s => s.trim()).filter(Boolean);\nconst names = config.team_names.split(',').map(s => s.trim()).filter(Boolean);\n\nconst today = new Date();\nconst standupId = 'standup_' + today.toISOString().substring(0, 10).replace(/-/g, '');\nconst standupDate = today.toISOString();\nconst dateStr = today.toLocaleDateString('en-US', {\n  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',\n});\n\nconst message = [\n  `\ud83d\udccb <b>Weekly Standup \u2014 ${dateStr}</b>\\n`,\n  `Hi! Please reply to this message with your update:\\n`,\n  `<b>1.</b> ${config.question_1}`,\n  `<b>2.</b> ${config.question_2}`,\n  `<b>3.</b> ${config.question_3}\\n`,\n  `Reply directly in this chat. Have a great week! \ud83d\udcaa`,\n].join('\\n');\n\nreturn chatIds.map((id, i) => ({\n  json: {\n    standup_id: standupId,\n    standup_date: standupDate,\n    chat_id: id,\n    name: names[i] || `Member ${i + 1}`,\n    message: message,\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "8387c800-bf91-4158-9bb9-e8254c60b405",
      "name": "\ud83d\udcf2 Send to Team",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1280,
        2192
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $json.chat_id }}",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "typeVersion": 1.2,
      "continueOnFail": true
    },
    {
      "id": "7cde8f21-7e07-410c-b2f1-7ca204b9042d",
      "name": "\ud83d\udcbe Log Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1504,
        2192
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "standup_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "standup_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sent",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "sent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ok",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "ok",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Standup_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "93de7d46-3e11-4903-ab93-8c58f9b3efa5",
      "name": "\u23f3 Wait 4 Hours",
      "type": "n8n-nodes-base.wait",
      "position": [
        1728,
        2192
      ],
      "parameters": {
        "unit": "hours",
        "amount": 4
      },
      "typeVersion": 1.1
    },
    {
      "id": "78e17588-e44e-4b86-8571-d945004d9943",
      "name": "\ud83d\udce5 Fetch Responses",
      "type": "n8n-nodes-base.code",
      "position": [
        1952,
        2192
      ],
      "parameters": {
        "jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// FETCH RECENT MESSAGES FROM TELEGRAM\n// Uses getUpdates to find replies sent\n// during the wait period\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst config = $('\u2699\ufe0f Settings').first().json;\nconst chatIds = config.team_chat_ids.split(',').map(s => s.trim());\nconst names = config.team_names.split(',').map(s => s.trim());\n\nconst prepared = $('\ud83d\udcdd Prepare Messages').all();\nconst standupId = prepared[0]?.json?.standup_id || 'unknown';\nconst standupDate = prepared[0]?.json?.standup_date || new Date().toISOString();\nconst standupTime = new Date(standupDate);\n\nlet botToken = '';\ntry {\n  botToken = config.telegram_bot_token || '';\n} catch (e) {}\n\nif (!botToken) {\n  return [{ json: {\n    standup_id: standupId,\n    standup_date: standupDate,\n    responses: [],\n    missing: names,\n    total_team: chatIds.length,\n    response_count: 0,\n    note: 'Add telegram_bot_token to Settings to auto-collect responses, or just send the AI summary based on what is in the chat manually.',\n  } }];\n}\n\ntry {\n  const r = await this.helpers.httpRequest({\n    method: 'GET',\n    url: `https://api.telegram.org/bot${botToken}/getUpdates`,\n    qs: { limit: 100, timeout: 0 },\n    timeout: 10000,\n  });\n  \n  const updates = r.result || [];\n  const responses = [];\n  const responded = new Set();\n  \n  for (let i = updates.length - 1; i >= 0; i--) {\n    const u = updates[i];\n    const msg = u.message;\n    if (!msg || !msg.text) continue;\n    \n    const fromId = String(msg.from?.id || '');\n    const idx = chatIds.indexOf(fromId);\n    if (idx === -1 || responded.has(fromId)) continue;\n    \n    const msgDate = new Date((msg.date || 0) * 1000);\n    if (msgDate < standupTime) continue;\n    \n    if (msg.text.startsWith('/')) continue;\n    if (msg.text.includes('Weekly Standup') && msg.text.includes('Reply directly')) continue;\n    \n    responded.add(fromId);\n    responses.push({\n      name: names[idx] || `Member ${idx + 1}`,\n      response: msg.text,\n      responded_at: msgDate.toISOString(),\n    });\n  }\n  \n  const missing = chatIds\n    .map((id, i) => ({ id, name: names[i] || id }))\n    .filter(m => !responded.has(m.id))\n    .map(m => m.name);\n  \n  return [{ json: {\n    standup_id: standupId,\n    standup_date: standupDate,\n    responses: responses,\n    missing: missing,\n    total_team: chatIds.length,\n    response_count: responses.length,\n  } }];\n  \n} catch (err) {\n  return [{ json: {\n    standup_id: standupId,\n    standup_date: standupDate,\n    responses: [],\n    missing: names,\n    total_team: chatIds.length,\n    response_count: 0,\n    error: err.message,\n  } }];\n}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "c12f903e-7915-47f8-b3e4-f8b802f288e6",
      "name": "\u23f0 Every Monday 9 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        608,
        2192
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6ac0e86a-5336-4326-a1b0-5db73ee2f924",
      "name": "\u2699\ufe0f Settings",
      "type": "n8n-nodes-base.set",
      "position": [
        832,
        2192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "field-team-ids",
              "name": "team_chat_ids",
              "type": "string",
              "value": "123456789,987654321,555555555"
            },
            {
              "id": "field-team-names",
              "name": "team_names",
              "type": "string",
              "value": "Ahmad,Sarah"
            },
            {
              "id": "field-lead-id",
              "name": "team_lead_chat_id",
              "type": "string",
              "value": "123456789"
            },
            {
              "id": "field-q1",
              "name": "question_1",
              "type": "string",
              "value": "\u2705 What did you accomplish last week?"
            },
            {
              "id": "field-q2",
              "name": "question_2",
              "type": "string",
              "value": "\ud83d\udccb What's planned for this week?"
            },
            {
              "id": "field-q3",
              "name": "question_3",
              "type": "string",
              "value": "\ud83d\udea7 Any blockers or issues?"
            },
            {
              "id": "field-groq-key",
              "name": "groq_api_key",
              "type": "string",
              "value": "PASTE_YOUR_GROQ_KEY_HERE"
            },
            {
              "id": "987aa1ca-3995-4f53-a29d-dac09d816fe1",
              "name": "telegram_bot_token",
              "type": "string",
              "value": "PASTE_YOUR_BOT_TOKEN_HERE"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "52f9fe22-75d3-4658-b1fc-91bf6f353c38",
      "name": "\ud83e\udd16 AI Summarize",
      "type": "n8n-nodes-base.code",
      "position": [
        2176,
        2192
      ],
      "parameters": {
        "jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// AI SUMMARIZE STANDUP via Groq\n// Smart retry on rate limit\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst config = $('\u2699\ufe0f Settings').first().json;\nconst data = $input.first().json;\nconst responses = data.responses || [];\nconst missing = data.missing || [];\n\nlet aiSummary = '';\nlet retriesUsed = 0;\n\nif (responses.length === 0) {\n  aiSummary = `\u26a0\ufe0f No responses collected this week.\\n\\nMissing: ${missing.join(', ')}\\n\\nNote: ${data.note || 'Team members may not have replied yet.'}`;\n} else {\n  let context = '';\n  for (const r of responses) {\n    context += `\\n${r.name}:\\n${r.response}\\n`;\n  }\n\n  const prompt = `You are a team standup summarizer. Below are individual standup updates from ${responses.length} team members. Create a concise executive summary in this exact format:\\n\\n\ud83d\udccc KEY ACCOMPLISHMENTS LAST WEEK\\n- [bullet point 1]\\n- [bullet point 2]\\n- [bullet point 3]\\n\\n\ud83c\udfaf THIS WEEK'S PLAN\\n- [bullet point 1]\\n- [bullet point 2]\\n- [bullet point 3]\\n\\n\ud83d\udea7 BLOCKERS & RISKS\\n- [list any blockers, or write \\\"None reported\\\"]\\n\\n\ud83d\udcaa TEAM HEALTH\\n[One sentence assessment: on track / needs attention / at risk]\\n\\nIndividual updates:\\n${context}`;\n\n  const maxRetries = 2;\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    try {\n      const r = await this.helpers.httpRequest({\n        method: 'POST',\n        url: 'https://api.groq.com/openai/v1/chat/completions',\n        headers: {\n          'Authorization': 'Bearer ' + config.groq_api_key,\n          'Content-Type': 'application/json',\n        },\n        body: {\n          model: 'llama-3.1-8b-instant',\n          messages: [{ role: 'user', content: prompt }],\n          temperature: 0.3,\n          max_tokens: 800,\n        },\n        timeout: 30000,\n      });\n      aiSummary = r.choices?.[0]?.message?.content || 'AI summary generation failed.';\n      break;\n    } catch (err) {\n      retriesUsed = attempt + 1;\n      const is429 = (err.message || '').includes('429');\n      if (is429 && attempt < maxRetries) {\n        await new Promise(r => setTimeout(r, 5000));\n        continue;\n      }\n      aiSummary = `\u26a0\ufe0f AI summary unavailable.\\n\\nRaw responses:\\n${context}`;\n      break;\n    }\n  }\n}\n\nconst dateStr = new Date(data.standup_date).toLocaleDateString('en-US', {\n  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',\n});\n\nlet finalMessage = `\ud83d\udccb <b>Weekly Standup Summary</b>\\n`;\nfinalMessage += `\ud83d\udcc5 ${dateStr}\\n`;\nfinalMessage += `\ud83d\udc65 Responded: ${responses.length}/${data.total_team}\\n`;\nif (missing.length > 0 && responses.length > 0) {\n  finalMessage += `\u26a0\ufe0f Missing: ${missing.join(', ')}\\n`;\n}\nfinalMessage += `\\n${aiSummary}`;\n\nreturn [{ json: {\n  ...data,\n  ai_summary: aiSummary,\n  final_message: finalMessage,\n  team_lead_chat_id: config.team_lead_chat_id,\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "415e89f6-fc09-4a80-ba09-66b0ab337b72",
      "name": "\ud83d\udcf2 Send Summary to Lead",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2400,
        2192
      ],
      "parameters": {
        "text": "={{ $json.final_message }}",
        "chatId": "={{ $json.team_lead_chat_id }}",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2,
      "continueOnFail": true
    },
    {
      "id": "b59e8e64-4ea5-4b55-917c-2d02f893b9c0",
      "name": "Note: Step 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2320,
        2032
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 428,
        "content": "## Step 5: Deliver Summary\n\nSends the AI-generated summary to the team lead via Telegram with per-person updates, blockers, and action items."
      },
      "typeVersion": 1
    },
    {
      "id": "0d38ed74-d214-4665-879e-07cad885657d",
      "name": "Note: Overview1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        48,
        1840
      ],
      "parameters": {
        "width": 480,
        "height": 620,
        "content": "## \ud83d\udccb Weekly Standup with AI Summary\n\n### How it works\n1. Fires every Monday at 9:00 AM and sends standup questions to each team member via Telegram.\n2. Logs sent messages to Google Sheets for tracking.\n3. Waits 4 hours for team members to reply.\n4. Fetches all responses from Telegram using the Bot API.\n5. Sends all replies to Groq AI (free) to generate a structured summary.\n6. Delivers the AI summary to the team lead via Telegram.\n\n### Setup steps\n- [ ] Create a Telegram bot via @BotFather and note the bot token\n- [ ] Get each team member's Telegram chat ID (via @userinfobot)\n- [ ] Get a free Groq API key at [console.groq.com](https://console.groq.com)\n- [ ] Create a Google Sheet for logging sent standups\n- [ ] Configure the Settings node with team member IDs, questions, and lead chat ID\n- [ ] Connect Telegram and Google Sheets credentials to the workflow nodes\n\n### Customization\n- Change the schedule (default: Monday 9 AM) in the trigger node\n- Edit standup questions in the Settings node\n- Adjust the wait time (default: 4 hours) in the Wait node\n- Modify the AI summary prompt to focus on blockers, priorities, or action items"
      },
      "typeVersion": 1
    },
    {
      "id": "942450b8-c822-440c-bd6d-3edb7387a494",
      "name": "Note: Step ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        2032
      ],
      "parameters": {
        "color": 7,
        "width": 444,
        "height": 428,
        "content": "## Step 1: Trigger & Settings\n\nFires every Monday at 9 AM. Edit **\u2699\ufe0f Settings** to configure team chat IDs, names, standup questions, and API keys.\nUse **\u25b6\ufe0f Manual Test** to run anytime."
      },
      "typeVersion": 1
    },
    {
      "id": "4039afdd-e3ab-4157-9665-cc9da87ed8e5",
      "name": "Note: Step 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        2032
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 428,
        "content": "## Step 2: Send & Log\n\nBuilds personalized standup messages for each team member, sends them via Telegram, and logs the delivery in Google Sheets for tracking."
      },
      "typeVersion": 1
    },
    {
      "id": "fd06161b-3dde-4111-9641-82f394e618c8",
      "name": "Note: Step 7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        2032
      ],
      "parameters": {
        "color": 7,
        "width": 224,
        "height": 428,
        "content": "## Step 3: Wait\n\nPauses for **4 hours** to give the team time to reply.\n\u26a0\ufe0f For testing, change to 2 minutes."
      },
      "typeVersion": 1
    },
    {
      "id": "a02aa99a-dcff-4ab1-bfa2-6e17fbb498a3",
      "name": "Note: Step 8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        2032
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 428,
        "content": "## Step 4: Collect & AI Summary\n\nFetches Telegram replies via Bot API, then sends all responses to Groq (free LLM) which generates a summary with key themes, blockers, and action items."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "\ud83d\udcbe Log Sent": {
      "main": [
        [
          {
            "node": "\u23f3 Wait 4 Hours",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2699\ufe0f Settings": {
      "main": [
        [
          {
            "node": "\ud83d\udcdd Prepare Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u23f3 Wait 4 Hours": {
      "main": [
        [
          {
            "node": "\ud83d\udce5 Fetch Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcf2 Send to Team": {
      "main": [
        [
          {
            "node": "\ud83d\udcbe Log Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udd16 AI Summarize": {
      "main": [
        [
          {
            "node": "\ud83d\udcf2 Send Summary to Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udce5 Fetch Responses": {
      "main": [
        [
          {
            "node": "\ud83e\udd16 AI Summarize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u23f0 Every Monday 9 AM": {
      "main": [
        [
          {
            "node": "\u2699\ufe0f Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcdd Prepare Messages": {
      "main": [
        [
          {
            "node": "\ud83d\udcf2 Send to Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}