Build a Personal Meeting Notetaker That Extracts Action Items for Free
What We're Building & Feature List
We are building a completely free, offline-first personal meeting notetaker. You drop an audio file into a folder on your laptop, and within seconds you get a markdown document containing a clean summary, key decisions, and a list of assigned action items.
This isn't a SaaS wrapper. We run the transcription entirely locally using Whisper.cpp to keep your meeting audio private. Only the text leaves your machine, hitting Groq's free inference tier to structure the notes. This guide is for engineers who want a reliable, zero-cost pipeline without vendor lock-in.
Feature List:
- Watch-folder automation: drop an audio file (WAV, MP3) and forget it.
- 100% local transcription via Whisper.cpp (no API keys, no uploads).
- Cloud extraction of summary, decisions, and action items using Groq's free Llama 3 models.
- Outputs a clean
meeting_notes.mdfile next to the original audio. - Runs on CPU for transcription; no GPU required (though it helps).
Architecture: The Data Flow
This pipeline follows a strict local-then-cloud separation. The only data that crosses the network boundary is the transcribed text—never raw audio.
The beauty here is modularity. If Groq changes their free tier limits tomorrow, you swap the LLM node out for Ollama running locally. If you want to use OpenAI's Whisper API instead of local Whisper.cpp, you swap the Execute Command node for an HTTP Request node. The pipeline is a chain of swappable black boxes.
Prerequisites: Free Tier Everything
Every tool in this stack has a genuinely free tier—no credit card required, no trial expiration.
| Tool | Purpose | Free Tier Limit | Link |
|---|---|---|---|
| n8n | Workflow automation | Unlimited self-hosted | n8n.io/downloads |
| Whisper.cpp | Local STT transcription | Unlimited local runs | github.com/ggerganov/whisper.cpp |
| Groq Cloud | LLM inference (Llama 3) | ~30 requests/min, generous daily tokens | console.groq.com |
| ggml model | Whisper model weights | Download once | huggingface.co/ggerganov |
Hardware expectation: Whisper.cpp base.en model runs on any laptop from the last 5 years. Transcription of a 30-minute meeting takes ~2-3 minutes on a modern CPU. If you have an Apple Silicon Mac, it'll fly.
Step 1: Setting Up Whisper.cpp for Local Transcription
We compile Whisper.cpp from source. This gives us a single binary we can call from n8n's Execute Command node.
# Clone and build
git clone https://github.com/ggerganov/whisper.cpp.git
cd whisper.cpp
make
# Download a model (base.en is ~150MB, great balance of speed/accuracy)
bash ./models/download-ggml-model.sh base.en
# Test it manually
./main -m models/ggml-base.en.bin -f ~/meeting_audio/test.wav -otxt
After that test, you should see test.wav.txt appear in the same directory with your transcript. We'll automate exactly this command from n8n.
Critical note on audio format: Whisper.cpp expects 16kHz mono WAV. Most meeting recorders output stereo MP3 or M4A. We'll handle conversion in the n8n workflow, but for now, know that you can pipe FFmpeg in front of it if needed:
ffmpeg -i meeting.m4a -ar 16000 -ac 1 -c:a pcm_s16le meeting.wav
Step 2: Building the n8n Watch Folder Trigger
Start n8n locally (Docker or npm). We'll use the npm approach for simplicity:
npx n8n
Open http://localhost:5678, create a new workflow.
Node 1: Local File Trigger
- Add a Local File Trigger node.
- Set the watch folder to an absolute path, e.g.,
/Users/yourname/meetings/inbox. - Set trigger to "File Created".
- Under Options, enable "Watch Folder Recursively" if you want subfolders.
Node 2: Move File (optional but recommended)
- Add a Move File node immediately after the trigger.
- Move from the triggered file path to a processing folder, e.g.,
/Users/yourname/meetings/processing/{{ $json.fileName }}. - This prevents the trigger from firing again on the same file if n8n restarts.
Step 3: Executing Whisper.cpp from n8n
This is the core integration. We use the Execute Command node to shell out to the Whisper.cpp binary.
Node 3: Execute Command
- Command:
=/Users/yourname/whisper.cpp/main - Arguments (one per line):
-m /Users/yourname/whisper.cpp/models/ggml-base.en.bin -f {{ $json.filePath }} -otxt -of {{ $json.filePath.replace('.wav','').replace('.mp3','').replace('.m4a','') }} - Set "Execute in folder" to
/Users/yourname/whisper.cpp.
Why this works: The -otxt flag tells Whisper.cpp to output a .txt file. The -of flag sets the output path to the same name as the input, minus the audio extension. So meeting_2025.wav produces meeting_2025.txt in the same directory.
FFmpeg pre-processing (if needed): If your recordings aren't 16kHz mono WAV, insert another Execute Command node before the Whisper call:
# Command: ffmpeg
# Arguments:
-i
{{ $json.filePath }}
-ar
16000
-ac
1
-c:a
pcm_s16le
{{ $json.filePath.replace('.mp3','_converted.wav').replace('.m4a','_converted.wav') }}
Then point the Whisper node at the _converted.wav file.
Step 4: Crafting the Groq Extraction Prompt
Now we have raw transcript text. We need to read it and send it to Groq.
Node 4: Read Binary File
- Add a Read Binary Files From Disk node.
- File path:
={{ $json.filePath.replace('.wav','.txt').replace('.mp3','.txt').replace('.m4a','.txt') }} - Property name:
transcriptText
Node 5: HTTP Request (Groq)
- Method: POST
- URL:
https://api.groq.com/openai/v1/chat/completions - Authentication: Header Auth
- Header Name:
Authorization - Header Value:
Bearer YOUR_GROQ_API_KEY
- Header Name:
- Body (JSON):
{
"model": "llama-3.1-70b-versatile",
"messages": [
{
"role": "system",
"content": "You are a precise meeting summarizer. Extract exactly three sections from the transcript: Summary (3-4 sentences), Key Decisions (bullet list), and Action Items (table with columns: Task, Assignee, Deadline). If an assignee or deadline is not explicitly stated, write 'Unassigned' or 'Not specified'. Output clean markdown. Do not hallucinate names or dates."
},
{
"role": "user",
"content": "Here is the meeting transcript:\n\n{{ $json.transcriptText }}"
}
],
"temperature": 0.2,
"max_tokens": 2048
}
Why Llama 3.1 70B on Groq? It's free, it's fast (usually under 2 seconds for this task), and its instruction following on structured extraction is excellent. The temperature: 0.2 keeps it factual. max_tokens: 2048 gives it room for long transcripts.
Rate limits: Groq's free tier allows roughly 30 requests per minute. For a single-user meeting notetaker, you'll never hit this. If you're processing back-to-back meetings, add a Wait node (5 seconds) between executions.
Step 5: Formatting the Final Output
Node 6: Code Node (JavaScript)
- Use a Code node to extract the LLM response and prepend metadata.
const response = $input.first().json.choices[0].message.content;
const originalFile = $('Move File').first().json.fileName;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const markdown = `# Meeting Notes: ${originalFile}
**Generated:** ${timestamp}
**Source:** Local Whisper.cpp + Groq (Llama 3.1 70B)
---
${response}
---
*Transcribed and summarized by your personal notetaker pipeline.*`;
return {
markdown,
fileName: originalFile.replace('.wav','').replace('.mp3','').replace('.m4a','') + '_notes.md'
};
Node 7: Write File to Disk
- Add a Write Binary File to Disk node.
- File path:
=/Users/yourname/meetings/outbox/{{ $json.fileName }} - Data property:
markdown
Now activate the workflow. Drop a meeting recording into ~/meetings/inbox/. Within minutes, you'll find structured notes in ~/meetings/outbox/.
How to Run the Full Pipeline
- Start n8n:
npx n8n(or keep it running as a service). - Ensure your watch, processing, and outbox directories exist.
- Set your Groq API key in the HTTP Request node (get one at console.groq.com).
- Activate the workflow (toggle in top-right).
- Drop a WAV file into the inbox folder.
- Watch the execution history in n8n for debugging; check the outbox for your notes.
Testing tip: Record a 2-minute test meeting with yourself. "Alice, Bob, and I decided to launch the feature by Friday. Alice will handle the API, Bob will update the docs." Drop it in. You should see Alice and Bob assigned correctly in the action items table.
Sensible Extensions
Once the core pipeline works, here's where you take it:
- Speaker diarization: Whisper.cpp doesn't natively do "who said what." If you need speaker labels, pre-process with
pyannote.audio(open source, free) and feed speaker-tagged transcripts to Groq. The prompt becomes: "Identify speakers and assign action items to them." - Auto-email action items: Add an Email node (Gmail or SMTP) after the Code node. Parse the action items table, and for each row with a named assignee, send a brief email: "You were assigned: [Task] with deadline [Date]."
- Calendar integration: Use n8n's Google Calendar node. If the meeting audio filename includes a date/time pattern, match it to a calendar event and attach the notes.
- Fully offline mode: Replace the Groq node with a local Ollama call (
http://localhost:11434/api/chat). Llama 3.1 8B runs on most laptops and handles extraction well. Your entire pipeline becomes air-gapped. - Mobile ingestion: Pair with n8n's webhook trigger. Use iOS Shortcuts or Android Tasker to upload recordings to a synced folder (iCloud, Syncthing) that n8n watches.
Common Pitfalls & Debugging
"Execute Command node hangs forever."
Whisper.cpp can take minutes on CPU. n8n's default timeout is 30 seconds. In the Execute Command node, go to Options > Timeout and set it to 600 (10 minutes). Also ensure the binary path is absolute and executable (chmod +x main).
"Groq returns garbled or truncated output." Your transcript might exceed the context window. Llama 3.1 70B has a 128k context window, so this is rare. But if your meeting is 3+ hours, chunk the transcript in a Code node before sending. Process 15,000-token chunks sequentially.
"The action items table has hallucinated names."
This happens when the transcript is noisy or the model tries too hard. Lower temperature to 0.1. Add to the system prompt: "If you cannot determine a name from the transcript, write 'Unassigned'. Never invent names."
"Whisper.cpp says 'failed to read WAV file'."
Your audio isn't 16kHz mono PCM. Run ffprobe yourfile.wav to check. If it's MP3 or M4A, insert the FFmpeg conversion step from Step 3.
"n8n trigger fires twice on the same file." Some OSes fire multiple filesystem events on file creation. Use the Move File node immediately after the trigger. Once moved, subsequent events on the original path are harmless.
If you're building pipelines like this regularly and want to go deeper into automation engineering, the FDE Mock Interview Blueprint covers exactly these kinds of system integration scenarios—debugging live data flows under time pressure. It's the same muscle.
FAQ
Q: Is this really all free? Yes. Whisper.cpp is open source and runs locally. n8n is free self-hosted. Groq provides a generous free tier for their API. You pay only for electricity and disk space.
Q: What about very long meetings (2+ hours)?
The base.en model handles long audio fine. The bottleneck becomes Groq's context window. For meetings over 2 hours, implement chunking: split the transcript at natural breaks (e.g., double newlines) and process each chunk, then combine summaries.
Q: Can I use this on Windows? Yes. Whisper.cpp compiles on Windows via MSVC or WSL2. The n8n workflow paths will use backslashes. Everything else is identical.
Q: How private is this really? Audio never leaves your machine. Only the transcribed text hits Groq's API. If you need full privacy, swap Groq for local Ollama as described in the Extensions section. Then zero data leaves your network.
Q: What's the accuracy like?
Whisper.cpp base.en is surprisingly good for clear meeting audio—typically 95%+ word error rate on clean speech. Heavy accents, overlapping talkers, or poor mics will degrade it. For critical meetings, use the medium.en model (~1.5GB, slower but more accurate).
Q: How does this compare to Otter.ai or Fireflies? Those are polished SaaS products with speaker diarization, search, and integrations. This pipeline is a hackable, private, zero-cost alternative you fully control. For a deeper look at building production-grade internal tools like this, see How FDEs Work with Product and Engineering After the Sale—the same pattern of shipping fast, iterating with users, and owning the stack.
Q: My action items aren't being extracted correctly. Any prompt tips? Constrain the output format explicitly. Add to the system prompt: "Output the Action Items section as a markdown table with exactly three columns: | Task | Assignee | Deadline |. Do not add extra columns. If a field is missing, write 'N/A'." Structured output prompting is an art—if you want to master this, the Build a Resume Tailoring Chrome Extension guide goes deep on prompt engineering for structured extraction.
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