Build a Daily Standup Bot with n8n and Gemini (Free Tier)
What We’re Building
A fully automated daily standup bot that runs on free-tier infrastructure. Every weekday at a set time, it DMs each team member a standup prompt, collects their replies via a webhook, hands the raw responses to Gemini for summarization, and drops a clean, structured summary into a public Slack channel.
No databases. No paid n8n cloud. No premium LLM endpoints. Just n8n (self-hosted or free cloud credits), the Slack API, and Google Gemini’s free tier.
Feature List
- Cron-driven execution – runs automatically on a schedule you define
- Multi-user DM collection – sends personalized prompts to a configurable list of teammates
- Interactive webhook receiver – collects responses through Slack’s interactive messages or a simple reply-to-bot flow
- Timeout handling – waits a configurable window for responses, then proceeds even if some people haven’t replied
- LLM summarization – uses Gemini 1.5 Flash (free tier) to distill raw updates into a concise standup summary
- Slack channel post – publishes the final summary to a designated channel with proper formatting
- Error resilience – handles API failures, empty responses, and network blips gracefully
Architecture
The flow is linear with one critical loop: the cron trigger fires, n8n iterates over your team list and sends each person a DM. A Wait node pauses execution for a fixed window (e.g., 15 minutes) while a companion Webhook node collects incoming responses. Once the window closes, all collected responses are aggregated, fed to Gemini, and the resulting summary is posted to Slack.
Prerequisites
Everything here is free-tier or open-source. No credit card required for the core pieces.
| Tool | Purpose | Free Tier Details |
|---|---|---|
| n8n | Workflow automation engine | Self-host via Docker (completely free) or n8n.cloud free tier (5 workflows, 500 executions/month) |
| Slack API | Messaging and bot integration | Free for any Slack workspace; bots don’t count as paid seats |
| Google Gemini API | LLM summarization | Gemini 1.5 Flash: 15 requests/minute free, 1M token context window |
Download/install links:
- n8n self-hosted:
docker run -it --rm --name n8n -p 5678:5678 n8nio/n8n - n8n cloud (free tier): https://n8n.io/cloud
- Gemini API key: https://aistudio.google.com/apikey
- Slack API apps: https://api.slack.com/apps
Step 1: Slack App Setup
Create a Slack app with the exact scopes needed for DM sending, message reading, and channel posting.
- Go to https://api.slack.com/apps and click Create New App → From scratch
- Name it
Standup Bot, pick your workspace - Under OAuth & Permissions, add these Bot Token Scopes:
chat:write– send DMs and channel messageschat:read– read responses in DMsusers:read– look up user IDschannels:read– find your target channel
- Click Install to Workspace and copy the Bot User OAuth Token (starts with
xoxb-) - Under Event Subscriptions, enable events and set the Request URL to your n8n webhook URL (we’ll generate this in Step 6). Subscribe to
message.imevents so the bot can see DM replies.
Important: The bot cannot DM itself. Make sure the bot user is not in your team member list.
Step 2: Gemini API Key
- Visit https://aistudio.google.com/apikey
- Click Create API Key, select a Google Cloud project (or create a new one)
- Copy the key—it’s a long string starting with
AIza - Test it immediately:
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"contents":[{"parts":[{"text":"Say hello in exactly 3 words."}]}]}'
You should get a JSON response with "text": "Hello from Gemini." or similar.
Step 3: n8n Workflow Scaffold
Open your n8n instance and create a new workflow. We’ll build it node by node, but here’s the mental model: Trigger → Loop Users → Send DM → Wait → Collect → Aggregate → Summarize → Post.
Environment variables to set in n8n:
SLACK_BOT_TOKEN=xoxb-your-token
GEMINI_API_KEY=AIza-your-key
TEAM_MEMBERS=["U12345","U67890"]
STANDUP_CHANNEL=C12345
WAIT_MINUTES=15
Use n8n’s Settings → Environment Variables or pass them via docker run -e if self-hosting.
Step 4: Cron Trigger
Add a Schedule Trigger node. Configure it for weekdays at your preferred standup time:
{
"rule": {
"interval": [{
"field": "cronExpression",
"expression": "0 9 * * 1-5"
}]
}
}
This fires at 9:00 AM Monday through Friday. Adjust the cron expression to your timezone (n8n uses the server’s timezone).
Step 5: DM Collection Loop
Add a Loop Over Items node connected to the Schedule Trigger. The loop iterates over $env.TEAM_MEMBERS parsed as JSON.
Inside the loop, add an HTTP Request node (method: POST) to call chat.postMessage:
// URL
https://slack.com/api/chat.postMessage
// Headers
Authorization: Bearer {{$env.SLACK_BOT_TOKEN}}
Content-Type: application/json
// Body (JSON)
{
"channel": "{{$json}}",
"text": ":wave: Good morning! What are you working on today? Any blockers?\n\nReply in this thread with your standup update.",
"unfurl_links": false
}
The $json variable holds each user ID from the loop. The bot sends the prompt as a DM because the channel parameter accepts user IDs.
Pro tip: Store the timestamp of each sent message. You’ll need it to match responses if you want per-user tracking, but for a basic summary, we just collect all replies in the window.
Step 6: Webhook Receiver
This is the trickiest part: collecting DM replies asynchronously while the main workflow waits.
Architecture decision: Instead of a single monolithic workflow, use two workflows or a Wait node paired with a Webhook node that stores responses in n8n’s workflow-static data.
Simplest approach (single workflow):
- After the DM loop completes, add a Wait node set to
{{$env.WAIT_MINUTES}}minutes - Add a Webhook node before the Wait node in a parallel branch (use a Split In node if needed). The webhook listens for Slack event payloads:
Webhook path: /standup-response
Method: POST
Response mode: Last Node
-
In the Slack App’s Event Subscriptions, set the Request URL to your n8n webhook URL +
/standup-response. Verify the URL (Slack sends a challenge). -
The Webhook node receives every
message.imevent. Filter for messages from your team members (checkevent.useragainst$env.TEAM_MEMBERS). Store the text in a workflow data array:
// In a Function node after the Webhook
const responses = $getWorkflowStaticData('global').responses || [];
responses.push({
user: $json.body.event.user,
text: $json.body.event.text,
ts: $json.body.event.ts
});
$getWorkflowStaticData('global').responses = responses;
return { collected: responses.length };
Alternative (easier, two workflows): Use a dedicated “collector” workflow with a Webhook trigger that appends to a Google Sheet or n8n’s static data. The main workflow reads from that store after the Wait node expires. This decouples collection from execution and is more reliable.
For this guide, we’ll use the two-workflow pattern for clarity.
Collector Workflow (standalone):
- Webhook Trigger → Function (filter + store) → Respond to Webhook (200 OK)
- The Function node writes to
$getWorkflowStaticData('global')
Main Workflow (after Wait node):
- Function node reads
$getWorkflowStaticData('global').responsesand passes it downstream
Step 7: Gemini Summarization
Add an HTTP Request node pointed at the Gemini API. The free tier uses gemini-1.5-flash—fast, capable, and well within rate limits.
// URL
https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={{$env.GEMINI_API_KEY}}
// Method: POST
// Headers
Content-Type: application/json
// Body (JSON)
{
"contents": [{
"parts": [{
"text": "You are a standup bot. Summarize the following team updates into a clear, structured standup summary. Group by themes, highlight blockers, and keep it concise.\n\nTeam updates:\n{{$json.responses}}"
}]
}],
"generationConfig": {
"temperature": 0.3,
"maxOutputTokens": 500
}
}
The $json.responses variable should be a formatted string of all collected updates. Use a Function node before this to shape the data:
const responses = $getWorkflowStaticData('global').responses || [];
const formatted = responses.map(r => `User ${r.user}: ${r.text}`).join('\n');
return { responses: formatted, count: responses.length };
Extract the summary from the Gemini response using another Function node:
const candidates = $json.body.candidates;
if (!candidates || candidates.length === 0) {
throw new Error('No summary generated');
}
return { summary: candidates[0].content.parts[0].text };
Step 8: Post to Channel
The final node: an HTTP Request to chat.postMessage targeting your standup channel.
// URL
https://slack.com/api/chat.postMessage
// Headers
Authorization: Bearer {{$env.SLACK_BOT_TOKEN}}
Content-Type: application/json
// Body
{
"channel": "{{$env.STANDUP_CHANNEL}}",
"text": "*:mega: Daily Standup Summary*\n\n{{$json.summary}}\n\n_Collected {{$json.count}} responses. Generated by Gemini._",
"mrkdwn": true
}
Cleanup: Add a final Function node to reset the static data so tomorrow’s run starts fresh:
$getWorkflowStaticData('global').responses = [];
return { success: true };
How to Run It
- Self-hosted:
docker run -d --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n n8nio/n8n - Import the workflow JSON (export from n8n editor)
- Set all environment variables in your Docker command or
.envfile - Expose your n8n instance to the internet for Slack webhooks. Use ngrok (free):
ngrok http 5678 - Update the Slack App’s Event Subscription URL to
https://your-ngrok.ngrok.io/webhook/standup-response - Activate the workflow and trigger a test run manually
Cloud alternative: Use n8n.cloud’s free tier. You get 500 executions/month—enough for ~16 daily runs. The webhook URL is provided automatically.
Sensible Extensions
- Per-user threading: Store the DM’s
tsvalue and reply in-thread with a thank-you or follow-up. Makes the bot feel conversational. - Sentiment analysis: Add a second Gemini call that scores each update’s sentiment (blocked/neutral/positive) and flags concerning responses.
- Historical context: Store summaries in a Google Sheet (free) and pass yesterday’s summary to Gemini as context for today’s summary. Creates continuity.
- Smart follow-ups: If someone reports a blocker two days in a row, have the bot ping a manager. This is where automation crosses into real operational value—similar to the pattern we explore in How Palantir-Style FDEs Embed with Customers: Rituals, Artifacts, and Trust.
- Multi-channel support: Post different summaries to different channels (engineering vs. leadership) by adjusting the Gemini prompt for each audience.
Common Pitfalls
-
Webhook URL not verified. Slack requires a challenge-response handshake. n8n handles this automatically if you use the Webhook node’s “Respond to Webhook” with the challenge token. If verification fails, check that your n8n instance is publicly accessible.
-
Rate limiting on Gemini free tier. 15 RPM is generous for a daily standup, but if you add per-user sentiment analysis, you might hit it. Add a Wait node between Gemini calls (1-2 seconds) or batch prompts.
-
DM fails silently. If the bot tries to DM a user it can’t reach (deactivated account, privacy settings), Slack returns
channel_not_found. Add error handling in the HTTP Request node: checkresponse.okand log failures. -
Static data persistence. n8n’s workflow static data survives between executions but resets on workflow deactivation. If you deactivate to edit, you lose collected responses. Use a Google Sheet for production-grade persistence.
-
Timezone confusion. The Schedule Trigger uses the server’s timezone. If your n8n container runs UTC but your team is in PST, adjust the cron expression or set
TZenvironment variable:docker run -e TZ=America/Los_Angeles ... -
Empty summaries on quiet days. If no one responds, Gemini might hallucinate or return a generic message. Add a guard: if
responses.length === 0, skip Gemini and post a “No updates today” message directly.
FAQ
Q: Can I use this with more than 5 team members?
Yes. The loop handles any number of users. Watch Slack’s rate limit for chat.postMessage (1 per second per channel). Add a 1-second Wait between DM sends if you have a large team.
Q: What if someone replies after the wait window? Their response is ignored for today’s summary. You could extend the collector workflow to handle late responses by timestamp-checking, but for simplicity, the window is fixed.
Q: Does Gemini read all the raw messages? Are they private? Yes, the raw text is sent to Google’s API. Review Google’s data usage policy for the free tier. For sensitive internal comms, consider self-hosting an open-weight model instead—a pattern we analyze in Why Open-Weight AI Is Repeating the Kubernetes Operational Playbook.
Q: Can I swap Gemini for another free LLM? Absolutely. Replace the HTTP Request node with a call to Groq (free tier, Llama 3), Mistral’s free API, or a locally hosted Ollama instance. The pattern is identical; only the endpoint and payload shape change.
Q: How do I debug when the summary doesn’t post? Check n8n’s execution history. Each node shows input/output. The most common failure points: invalid Gemini API key (401), Slack token expired (revoked in admin panel), or webhook URL unreachable (ngrok tunnel down).
Q: This feels like a prototype. What does it take to make this production-grade at an enterprise? That’s the exact transition Forward Deployed Engineers handle daily. You’d add authentication, persistent storage, error recovery, monitoring, and probably swap Gemini for a fine-tuned model on your own infrastructure. For a deep dive on that handoff from prototype to core engineering, see Scaling Yourself: When an FDE Hands Off a Prototype to Core Engineering.
Q: I’m an engineer looking to build these kinds of integrations professionally. What’s the career path? Forward Deployed Engineering sits at the intersection of engineering, product, and customer success. You build exactly these kinds of prototypes—fast, pragmatic, customer-facing—and then scale them. If that sounds like your sweet spot, FDE Coach exists to help you master the craft: from n8n workflows to enterprise LLM deployments to the communication skills that make technical prototypes land with executives." }
Want to build like a Forward Deployed Engineer?
FDE Coach is a cohort-based program in frontend, backend, AWS, and AI. Build real products and get referred to 200+ hiring partners.
Explore the program