Your meeting lasted 45 minutes. The transcript is 8,000 words. Nobody wants to read it. Everyone needs the action items.
In this tutorial, weβll build a web app that takes a meeting transcript (pasted or uploaded) and generates structured notes: key decisions, action items with owners, open questions, and a 3-sentence summary.
What weβre building
Upload or paste a transcript β get structured notes:
π Meeting Summary (March 25, 2026 β Sprint Planning)
## TL;DR
Team committed to 34 story points. Payment refactor pushed to next sprint
due to dependency on the new API. New hire starts Monday β Alex is onboarding buddy.
## Decisions
- β
Ship the dashboard redesign by Friday
- β
Push payment refactor to Sprint 14 (blocked by API team)
- β
Alex onboards the new hire starting Monday
## Action Items
- @sarah: Finalize dashboard designs by Wednesday
- @jake: Write API dependency doc and share with payment team
- @alex: Prepare onboarding checklist by Friday
- @team: Review sprint board before Thursday standup
## Open Questions
- When will the new API be ready? (Jake to follow up)
- Do we need a design review for the mobile version?
The code
Backend (Express + Claude)
// server.js
import express from 'express';
import Anthropic from '@anthropic-ai/sdk';
const app = express();
app.use(express.json({ limit: '1mb' }));
app.use(express.static('public'));
const anthropic = new Anthropic();
app.post('/api/summarize', async (req, res) => {
const { transcript } = req.body;
if (!transcript) return res.status(400).json({ error: 'No transcript provided' });
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1500,
messages: [{
role: 'user',
content: `Summarize this meeting transcript into structured notes.
Format:
## TL;DR
[3 sentences max β what was this meeting about and what was decided]
## Decisions
[Bullet list of decisions made, prefixed with β
]
## Action Items
[Bullet list with @owner and deadline if mentioned]
## Open Questions
[Anything unresolved that needs follow-up]
Rules:
- Be specific β use names, dates, and details from the transcript
- If no decisions were made, say "No formal decisions"
- If no action items, say "No action items assigned"
- Skip small talk and off-topic discussion
Transcript:
${transcript.slice(0, 20000)}`
}],
});
res.json({ summary: response.content[0].text });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Running on http://localhost:3000'));
Frontend (simple HTML)
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Meeting Notes AI</title>
<style>
body { font-family: system-ui; max-width: 800px; margin: 2em auto; padding: 0 1em; }
textarea { width: 100%; height: 200px; padding: 1em; font-size: 14px; border: 2px solid #e5e7eb; border-radius: 8px; }
button { padding: 0.8em 2em; background: #4f46e5; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; margin-top: 1em; }
#result { margin-top: 2em; padding: 1.5em; background: #f9fafb; border-radius: 8px; white-space: pre-wrap; line-height: 1.6; }
.loading { color: #666; }
</style>
</head>
<body>
<h1>π Meeting Notes AI</h1>
<p>Paste your meeting transcript below.</p>
<textarea id="transcript" placeholder="Paste transcript here..."></textarea>
<button onclick="summarize()">Generate Notes</button>
<div id="result"></div>
<script>
async function summarize() {
const transcript = document.getElementById('transcript').value;
const result = document.getElementById('result');
result.innerHTML = '<p class="loading">β³ Generating notes...</p>';
const res = await fetch('/api/summarize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transcript }),
});
const data = await res.json();
result.textContent = data.summary || data.error;
}
</script>
</body>
</html>
Setup and run
mkdir meeting-notes && cd meeting-notes
npm init -y && npm install express @anthropic-ai/sdk
# Create server.js and public/index.html as above
ANTHROPIC_API_KEY=sk-ant-xxx node server.js
# Open http://localhost:3000
Making it better
- File upload: Accept
.txt,.vtt(Zoom transcripts), and.srtfiles - Otter.ai integration: Pull transcripts directly from Otterβs API
- Slack posting: Send the summary to a Slack channel after generation
- Multiple formats: Output as markdown, Notion block, or email draft
- Speaker detection: If the transcript has speaker labels, attribute action items to speakers
Total build time: ~30 minutes. API cost: ~$0.02-0.05 per meeting.
Related: Best Free AI APIs 2026 Β· How to Build an AI Agent Β· 5 AI Prompts for Debugging Β· Build Code Snippet Manager