All Workflows / AI Agents
AI Agents
Intermediate 15 min READ

Auto-summarize meeting notes into Notion

Pipe transcripts through Claude and push structured summaries straight into a Notion database.

Target Architecture

A serverless function or Node.js webhook handler that accepts a raw meeting transcript, sends it to Claude Sonnet with a specialized prompt requesting key decisions, action items, and discussion points, parses the response into structured Notion block objects, and writes a new page into your Notion database with tags and timestamps.

INTEGRATED RUNTIMES:

Tools used

Claude Notion Node.js
Table of Contents
8 Sections

Overview

This guide demonstrates how to build an automated pipeline that takes raw meeting transcripts (from Zoom, Otter.ai, Google Meet, or Whisper), sends them to Claude via the Anthropic Messages API for structured summarization, and creates a formatted page with executive summaries, key decisions, and action items inside a Notion database. Every technical term is explained in the Glossary below before it is used.

Before you begin

Budget about 20–30 minutes to set up your Notion integration token, share your database, configure your Anthropic API key, and test a sample transcript payload. Ongoing costs depend on meeting volume: Claude 3.5 Sonnet costs fractions of a cent to a few cents per meeting summary depending on transcript token count. This guide has been written from technical documentation and general best practice, but — like every guide currently on Workflow Vault — it is UNVERIFIED: it has not yet been run end-to-end and confirmed working by Workflow Vault's own testing. Review all code before running it in production.

Glossary

Anthropic Messages API
The official REST API endpoint provided by Anthropic for sending prompts and receiving text or structured analysis from Claude AI models.
Claude Sonnet
Anthropic's balanced model tier offering high reasoning accuracy, strong summarization skills, and fast response times at a cost-effective price.
Notion Integration Token
A secret bearer token (starting with `secret_` or `ntn_`) generated in Notion's developer portal that grants your automation script permission to read and create pages in Notion.
Notion Database ID
The unique 32-character alphanumeric identifier found in the URL of your Notion database (between the workspace name and the question mark) specifying where new pages should be inserted.
Notion Block
The fundamental unit of content in Notion (such as headings, bullet lists, callouts, and paragraphs). The Notion API requires page content to be passed as an array of block objects.
Token (LLM)
The basic unit of text that large language models process. As a rule of thumb, 1,000 tokens corresponds to roughly 750 English words.
Environment Variable
A secure named setting configured on your server or hosting platform to hold sensitive API keys without committing them directly into code repositories.
Webhook
An automated HTTP POST request sent by a service (such as Zoom or a transcription provider) immediately when an event occurs, triggering downstream processing.

Prerequisites

  • [You'll need this already] An Anthropic API key from console.anthropic.com with active credits or billing configured.
  • [You'll need this already] A Notion workspace where you have admin permissions to create an internal integration and a target database with a Name/Title column.
  • [You'll need this already] A Node.js environment (Node 18+ or Cloudflare Workers) capable of making fetch requests and reading environment variables.
  • [You'll need this already] Basic familiarity with JSON and async JavaScript functions. You do not need deep AI or Notion API experience — all requests and payload formats are provided below.
  • [Optional, not required] A sample transcript export file (VTT, TXT, or JSON) from Zoom, Otter.ai, or Google Meet to use during initial testing.

Where to Run This Automation

Where should you run this automation?

Don't have 24/7 hardware at home? Choose the setup that fits your budget and technical comfort.

Hardware & Cloud Guide

Best for Freelancers, Agencies, and 24/7 Client Pipelines

If you don't have a dedicated server running at home, a Virtual Private Server (VPS) gives you a dedicated Linux server in the cloud that stays online 24/7 even when your personal laptop is closed.

  • Cost: ~$4 to $6 / month (predictable flat fee, no per-execution surprises).
  • Providers: Hetzner Cloud, DigitalOcean, Linode / Akamai, Railway, or OVH.
  • Key advantage: You get a permanent public IP address and free SSL certificates, which are essential for receiving incoming Stripe, Airtable, or GitHub webhooks reliably.
Quick 1-liner to install Docker on any Ubuntu/Debian VPS: curl -fsSL https://get.docker.com | sh && sudo usermod -aG docker $USER
RECOMMENDED SPECS
RAM: 1GB – 2GB
vCPU: 1 Core
Storage: 20GB SSD
Uptime: 99.9%

Best for Hobbyists, Testing & Zero-Cost Experimentation

You can run this automation completely free on your everyday computer (Mac, Windows, or Linux) or on low-cost home hardware like a Raspberry Pi 4/5 or repurposed mini PC.

  • Cost: $0 / month (100% free).
  • Software needed: Docker Desktop (free for personal use) or native Node.js / Python.
  • Consideration: Automations will only execute while your computer is powered on and awake. To receive incoming webhooks locally, use tools like ngrok or Cloudflare Tunnels for secure local tunneling.
LOCAL CHECKLIST
Docker Desktop: Installed
RAM Overhead: ~400MB
Webhooks: Tunnel required

Best for Zero-Maintenance 1-Click Operations

If you don't want to manage Docker containers or terminal commands, you can run automations directly on managed serverless platforms or official SaaS plans:

  • Cloudflare Workers: Free tier includes 100,000 requests per day with 0ms cold starts.
  • n8n Cloud / Make / Zapier: Fully managed hosted platforms with automated backups and visual scenario builders.
  • Railway / Render: 1-click Git container deploys with auto-healing and managed PostgreSQL databases.
SERVERLESS TIERS
Cloudflare: 100k req/day free
GitHub CI: 2,000 min/mo free
Maintenance: Zero OS updates

Step-by-step implementation

Click the numbered phase buttons as you complete each step to track your live progress.

Phase Progress 0/5 completed (0%)
PHASE 1

Set up the Notion integration and database

Log into notion.so/profile/integrations and click 'New integration'. Name it 'Meeting Summarizer', select your workspace, and save it to obtain your Internal Integration Secret (starts with `secret_` or `ntn_`). Next, create or open your Notion database for meeting notes. Click the '...' menu at the top right of the database page, select 'Connections' or 'Add connections', and choose your 'Meeting Summarizer' integration to give it write access. Finally, copy the Database ID from the browser URL.

Why this matters

By default, Notion integrations have zero access to any page or database in your workspace. Explicitly sharing the database with the integration connection is required before the API can create pages inside it.

Expected Output

Your database settings should list 'Meeting Summarizer' under Connections, and you should have both your integration secret token and database ID ready.

PHASE 2

Call Claude to generate a structured summary

Create a function that sends the raw transcript to Anthropic's Messages API (`https://api.anthropic.com/v1/messages`). Use `claude-3-5-sonnet-20241022` or `claude-3-haiku-20240307` with a structured system prompt instructing Claude to output cleanly demarcated sections: Executive Summary, Key Decisions, Action Items (with assignees if mentioned), and Open Questions.

javascript
async function summarizeTranscript(transcript, meetingTitle) {
  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.ANTHROPIC_API_KEY,
      'anthropic-version': '2023-06-01',
      'content-type': 'application/json'
    },
    body: JSON.stringify({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 1500,
      temperature: 0.2,
      system: 'You are an executive assistant. Summarize meeting transcripts into structured sections: 1) Executive Summary, 2) Key Decisions, 3) Action Items, 4) Open Questions. Use clear bullet points.',
      messages: [{
        role: 'user',
        content: `Please summarize this meeting transcript titled "${meetingTitle}":\n\n${transcript}`
      }]
    })
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`Claude API error (${response.status}): ${err}`);
  }

  const data = await response.json();
  return data.content[0].text;
}
Why this matters

Supplying a structured format prompt forces Claude to return consistent, predictable sections rather than free-form conversational prose, making it simple to parse into individual Notion blocks.

Expected Output

Running this function with a test transcript returns Claude's response text containing clearly formatted headers and bullet points without extraneous pleasantries.

PHASE 3

Convert the markdown summary into Notion block objects

Write a lightweight converter function that turns the summary text into the block format required by Notion's API. Convert main section titles into `heading_2` blocks, bullet points into `bulleted_list_item` blocks, and standard paragraphs into `paragraph` blocks.

javascript
function textToNotionBlocks(summaryText) {
  const lines = summaryText.split('\n').filter(line => line.trim() !== '');
  const blocks = [];

  for (const line of lines) {
    const trimmed = line.trim();
    if (trimmed.startsWith('#') || trimmed.match(/^[0-9]+\)/) || trimmed.endsWith(':')) {
      const cleanHeader = trimmed.replace(/^#+\s*/, '').replace(/^[0-9]+\)\s*/, '');
      blocks.push({
        object: 'block',
        type: 'heading_2',
        heading_2: {
          rich_text: [{ type: 'text', text: { content: cleanHeader } }]
        }
      });
    } else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
      blocks.push({
        object: 'block',
        type: 'bulleted_list_item',
        bulleted_list_item: {
          rich_text: [{ type: 'text', text: { content: trimmed.replace(/^[-*]\s+/, '') } }]
        }
      });
    } else {
      blocks.push({
        object: 'block',
        type: 'paragraph',
        paragraph: {
          rich_text: [{ type: 'text', text: { content: trimmed } }]
        }
      });
    }
  }
  return blocks;
}
Why this matters

Notion does not accept raw markdown strings for page content; it expects an array of typed block objects. Converting the text beforehand ensures the Notion page has native formatting, headings, and clean list indentation.

Expected Output

The helper function produces an array of objects shaped like `{ object: 'block', type: 'heading_2', heading_2: { ... } }` matching the Notion block specification.

PHASE 4

Create the new page in your Notion database

Call the Notion API (`https://api.notion.com/v1/pages`) with `NOTION_TOKEN` in the Authorization header and `Notion-Version: 2022-06-28`. Pass the database ID as parent, the meeting title and date in properties, and the generated blocks as children.

javascript
async function createNotionMeetingPage(meetingTitle, meetingDate, summaryBlocks) {
  const response = await fetch('https://api.notion.com/v1/pages', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.NOTION_TOKEN}`,
      'Notion-Version': '2022-06-28',
      'content-type': 'application/json'
    },
    body: JSON.stringify({
      parent: { database_id: process.env.NOTION_DATABASE_ID },
      properties: {
        Name: {
          title: [{ type: 'text', text: { content: meetingTitle } }]
        }
      },
      children: summaryBlocks
    })
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`Notion API error (${response.status}): ${err}`);
  }

  return await response.json();
}
Why this matters

This step writes the final data to Notion. By passing the summary blocks in the initial page creation request, the page is populated in a single atomic API call rather than requiring multiple roundtrips.

Expected Output

The Notion API returns an HTTP 200 with the newly created page object. Opening your Notion database shows a new entry with the title, timestamp, and formatted summary blocks inside.

PHASE 5

Assemble the complete automation handler

Wire the steps together inside a single handler function that accepts an incoming transcript payload, invokes Claude for summarization, formats the blocks, and publishes to Notion with robust try/catch error logging.

javascript
async function processMeetingTranscript(transcript, meetingTitle = 'Untitled Meeting') {
  console.log(`Starting summarization for: ${meetingTitle}`);
  const summary = await summarizeTranscript(transcript, meetingTitle);
  const blocks = textToNotionBlocks(summary);
  const newPage = await createNotionMeetingPage(meetingTitle, new Date().toISOString(), blocks);
  console.log(`Successfully created Notion page: ${newPage.url}`);
  return newPage;
}
Why this matters

Packaging the full flow into an isolated handler allows it to be plugged directly into an Express route, AWS Lambda handler, or Cloudflare Worker endpoint triggered whenever a new transcript is ready.

Expected Output

Calling `processMeetingTranscript(sampleText, 'Sprint Planning')` executes the entire pipeline and logs the URL of the created Notion page.

Workflow architecture

A transcript source (such as a Zoom webhook or recording uploader) invokes a serverless execution environment. The function sends the raw text to Claude via the Anthropic Messages API, which extracts structured summary sections. A lightweight parser converts the summary text into standard Notion block objects, and the Notion API writes the new page into the target database. The pipeline remains completely stateless and safe to retry on failures.

Final result

Structured meeting summaries appear inside your Notion database within 5 to 15 seconds of a transcript becoming available, containing clean headings, key decisions, and actionable tasks with no manual copy-pasting required. As noted above, this guide is unverified by Workflow Vault — treat the outcome as expected-but-not-yet-confirmed until tested with your own credentials.

Recommended Extensions & Scaling

  • Add automated tagging for meeting categories (e.g. Sales, Sprint, 1-on-1) by prompting Claude to classify the meeting type in a frontmatter block.
  • Wire up automatic Slack or email notifications with a direct link to the created Notion page once the summary is ready.
  • Add fallback chunking for extra-long transcripts (meetings over 2 hours) to summarize sections before combining.

Troubleshooting

⚠ Claude API call returns 401 Unauthorized
Root Cause

ANTHROPIC_API_KEY environment variable is missing, incorrect, or expired.

Solution

Generate a new API key in the Anthropic Console, update your deployment environment variables, and restart your server or worker.

⚠ Notion API returns 404 with error code 'object_not_found'
Root Cause

The Notion integration has not been shared with the target database, or NOTION_DATABASE_ID is incorrect.

Solution

Open your Notion database in the browser, click '...' -> Connections, and add your integration. Verify the 32-character database ID in your URL.

⚠ Notion API returns 400 'validation_error' on blocks array
Root Cause

A block object in the array exceeded Notion's 2000-character limit per rich_text item, or an invalid block type was passed.

Solution

Ensure the text splitter chunks long paragraphs into segments under 2000 characters before building the rich_text array.

⚠ Summary text arrives truncated or cuts off mid-sentence
Root Cause

max_tokens parameter in the Claude API request is set too low for the transcript length.

Solution

Increase max_tokens to 2000 or 4000 in your Anthropic API request configuration.