This workflow corresponds to n8n.io template #15114 — we link there as the canonical source.
This workflow follows the Agent → Google Drive recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "jhCySM8mocQtyeqh",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Meeting Notes to Action Items Automator",
"tags": [],
"nodes": [
{
"id": "be31e6e4-a700-49d8-910b-4732919be1e1",
"name": "Sticky Note - Documentation",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-304
],
"parameters": {
"width": 820,
"height": 1536,
"content": "## Meeting Notes to Action Items Automator\n\nThis AI-powered workflow transcribes Zoom/Google Meet recordings, extracts decisions and tasks using AI, then creates tickets in Jira/ClickUp/Linear and assigns them to team members automatically.\n\n### How it works\n\n1. **Trigger** - Receives meeting recording URL via webhook or schedule\n2. **Download Recording** - Fetches audio/video file from Zoom/Google Meet\n3. **Audio Extraction** - Converts video to audio if needed using FFmpeg\n4. **Transcription** - Uses Whisper API to transcribe meeting audio\n5. **Wait & Process** - Allows transcription to complete\n6. **Parse Transcript** - Cleans and formats the transcription text\n7. **AI Analysis** - Claude extracts action items, decisions, owners\n8. **Team Member Matching** - Maps names to user IDs in project tools\n9. **Create Tasks** - Generates tickets in Jira/ClickUp/Linear\n10. **Assign & Notify** - Assigns tasks to team members and sends notifications\n11. **Meeting Summary** - Saves full summary to Google Drive/Notion\n12. **Response** - Returns processed action items and task links\n\n### Setup Steps\n\n1. Import this workflow into your n8n instance\n2. Configure credentials:\n - **Zoom OAuth** - For downloading Zoom recordings\n - **Google OAuth** - For Google Meet recordings and Drive storage\n - **OpenAI API** - For Whisper transcription service\n - **Anthropic API** - For Claude AI analysis\n - **Jira/ClickUp/Linear API** - For task creation\n - **Slack/Teams** - For notifications (optional)\n3. Set up team member mapping in the config node\n4. Configure your project management tool preferences\n5. Activate the workflow\n\n### Sample Trigger Payload\n```json\n{\n \"meetingSource\": \"zoom\",\n \"recordingUrl\": \"https://zoom.us/rec/share/...\",\n \"meetingTitle\": \"Q1 Planning Meeting\",\n \"meetingDate\": \"2024-01-15\",\n \"attendees\": [\"alice@company.com\", \"bob@company.com\", \"charlie@company.com\"],\n \"projectKey\": \"PROJ-123\",\n \"taskTool\": \"jira\",\n \"defaultPriority\": \"medium\",\n \"autoAssign\": true,\n \"sendNotifications\": true,\n \"saveToNotion\": false,\n \"saveToDrive\": true,\n \"extractDecisions\": true,\n \"extractRisks\": true,\n \"dueDate\": \"2024-01-22\"\n}\n```\n\n### Features\n\n- **Multi-platform support** (Zoom, Google Meet, MS Teams recordings)\n- **Accurate transcription** using OpenAI Whisper API\n- **AI-powered extraction** of action items, decisions, risks, and next steps\n- **Automatic task creation** in Jira, ClickUp, or Linear\n- **Smart assignment** - maps attendee names to task assignees\n- **Meeting summaries** - saves comprehensive notes to Drive/Notion\n- **Slack/Teams notifications** - alerts team members of new tasks\n- **Duplicate detection** - prevents creating duplicate tickets\n- **Priority detection** - AI assigns urgency levels to tasks"
},
"typeVersion": 1
},
{
"id": "46817c96-d413-4b88-8081-68569ffe226e",
"name": "Sticky Note - Section 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
288
],
"parameters": {
"color": 5,
"width": 656,
"height": 520,
"content": "## 1. Receive & Download Recording"
},
"typeVersion": 1
},
{
"id": "09e53bbc-68aa-4cbd-93da-3f8bd9d0847c",
"name": "Sticky Note - Section 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
48
],
"parameters": {
"color": 5,
"width": 700,
"height": 740,
"content": "## 2. Transcribe & Parse Audio"
},
"typeVersion": 1
},
{
"id": "787e1269-ff90-4414-b79a-fdd1b798cf1a",
"name": "Sticky Note - Section 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2144,
128
],
"parameters": {
"color": 5,
"width": 680,
"height": 660,
"content": "## 3. AI Analysis & Extraction"
},
"typeVersion": 1
},
{
"id": "db570ee0-925a-4527-bdb5-c7ce819f99fa",
"name": "Sticky Note - Section 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2896,
112
],
"parameters": {
"color": 5,
"width": 2080,
"height": 800,
"content": "## 4. Create Tasks & Notify"
},
"typeVersion": 1
},
{
"id": "9d970b7a-85f6-47a6-8fc3-c30055cb08e2",
"name": "Receive Meeting Recording Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
784,
512
],
"parameters": {
"path": "meeting-automator",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "0d000a90-4c74-4d7f-a858-5a759f5bee5b",
"name": "Validate Config & Build Params",
"type": "n8n-nodes-base.code",
"position": [
1008,
512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Extract and validate configuration from request\nconst body = $input.item.json.body || $input.item.json;\n\n// Required fields validation\nif (!body.recordingUrl) {\n throw new Error('recordingUrl is required');\n}\n\nif (!body.meetingSource || !['zoom', 'google_meet', 'teams'].includes(body.meetingSource)) {\n throw new Error('meetingSource must be one of: zoom, google_meet, teams');\n}\n\n// Build configuration\nconst config = {\n meetingSource: body.meetingSource,\n recordingUrl: body.recordingUrl,\n meetingTitle: body.meetingTitle || 'Untitled Meeting',\n meetingDate: body.meetingDate || new Date().toISOString().split('T')[0],\n attendees: body.attendees || [],\n projectKey: body.projectKey || 'GENERAL',\n taskTool: body.taskTool || 'jira', // jira, clickup, linear\n defaultPriority: body.defaultPriority || 'medium',\n autoAssign: Boolean(body.autoAssign !== false),\n sendNotifications: Boolean(body.sendNotifications !== false),\n saveToNotion: Boolean(body.saveToNotion),\n saveToDrive: Boolean(body.saveToDrive !== false),\n extractDecisions: Boolean(body.extractDecisions !== false),\n extractRisks: Boolean(body.extractRisks !== false),\n dueDate: body.dueDate || null,\n transcriptionLanguage: body.transcriptionLanguage || 'en'\n};\n\n// Team member mapping (email to user IDs in different platforms)\nconst teamMapping = body.teamMapping || {\n // Example structure - should be customized per organization\n 'user@example.com': {\n name: 'Alice Johnson',\n jiraAccountId: 'account-id-1',\n clickupUserId: '12345',\n linearUserId: 'linear-user-1',\n slackUserId: 'U01ABC123'\n },\n 'user@example.com': {\n name: 'Bob Smith',\n jiraAccountId: 'account-id-2',\n clickupUserId: '67890',\n linearUserId: 'linear-user-2',\n slackUserId: 'U02DEF456'\n }\n};\n\n// Metadata\nconst metadata = {\n workflowId: `MNA-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n startedAt: new Date().toISOString(),\n timezone: body.timezone || 'UTC'\n};\n\nreturn {\n json: {\n config,\n teamMapping,\n metadata,\n rawBody: body\n }\n};"
},
"typeVersion": 2
},
{
"id": "18f3c152-a677-4933-931b-1a4a6402805c",
"name": "Check Meeting Source",
"type": "n8n-nodes-base.if",
"position": [
1232,
512
],
"parameters": {
"options": {},
"conditions": {
"options": {
"caseSensitive": false
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.config.meetingSource }}",
"rightValue": "zoom"
}
]
}
},
"typeVersion": 2
},
{
"id": "b8ef3986-0a20-4ce1-8a37-4791770dc8ba",
"name": "Download Zoom Recording",
"type": "n8n-nodes-base.httpRequest",
"position": [
1488,
416
],
"parameters": {
"url": "={{ $json.config.recordingUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "zoomOAuth2Api"
},
"credentials": {
"zoomOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "83e6c901-25f3-4c1f-8e26-170e51542fe3",
"name": "Download Google Meet Recording",
"type": "n8n-nodes-base.httpRequest",
"position": [
1488,
608
],
"parameters": {
"url": "={{ $json.config.recordingUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleOAuth2Api"
},
"credentials": {
"googleOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "d540c3b5-b133-4cc6-9d51-305a8f7af6ce",
"name": "Detect File Type",
"type": "n8n-nodes-base.code",
"position": [
1728,
512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Check if file is video (needs audio extraction) or already audio\nconst binaryData = Object.values($input.item.binary)[0];\nconst fileName = binaryData.fileName || '';\nconst mimeType = binaryData.mimeType || '';\n\nconst isVideo = mimeType.includes('video') || fileName.match(/\\.(mp4|mov|avi|mkv)$/i);\nconst isAudio = mimeType.includes('audio') || fileName.match(/\\.(mp3|wav|m4a|ogg)$/i);\n\nreturn {\n json: {\n fileName,\n mimeType,\n isVideo,\n isAudio,\n needsConversion: isVideo,\n binaryKey: Object.keys($input.item.binary)[0],\n config: $('Validate Config & Build Params').item.json.config,\n metadata: $('Validate Config & Build Params').item.json.metadata\n },\n binary: $input.item.binary\n};"
},
"typeVersion": 2
},
{
"id": "4cf54e0c-0522-484f-bceb-ccf14da45537",
"name": "Transcribe Audio with Whisper",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1968,
608
],
"parameters": {
"options": {},
"operation": "classify"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6b67881c-2a73-4c34-bd40-ab6962bf5255",
"name": "Wait for Transcription",
"type": "n8n-nodes-base.wait",
"position": [
2208,
512
],
"parameters": {
"resume": "after-execution"
},
"typeVersion": 1.1
},
{
"id": "ee566051-4ee7-4a08-98d7-18268d495e8e",
"name": "Parse & Clean Transcript",
"type": "n8n-nodes-base.code",
"position": [
2448,
512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Parse and clean transcription result\nconst transcription = $input.item.json;\nlet transcriptText = transcription.text || transcription.transcription || '';\n\n// Clean up common transcription artifacts\ntranscriptText = transcriptText\n .replace(/\\[MUSIC\\]/g, '')\n .replace(/\\[INAUDIBLE\\]/g, '[unclear]')\n .trim();\n\n// Split into segments if available\nconst segments = transcription.segments || [];\n\n// Get meeting metadata from upstream\nconst config = $('Validate Config & Build Params').item.json.config;\nconst metadata = $('Validate Config & Build Params').item.json.metadata;\n\n// Calculate duration\nconst duration = segments.length > 0 \n ? segments[segments.length - 1].end \n : null;\n\n// Count words and speakers\nconst wordCount = transcriptText.split(/\\s+/).length;\nconst speakerCount = new Set(\n segments.filter(s => s.speaker).map(s => s.speaker)\n).size;\n\nreturn {\n json: {\n transcript: {\n fullText: transcriptText,\n segments,\n wordCount,\n speakerCount,\n durationSeconds: duration,\n language: config.transcriptionLanguage\n },\n meeting: {\n title: config.meetingTitle,\n date: config.meetingDate,\n attendees: config.attendees,\n projectKey: config.projectKey\n },\n config,\n metadata\n }\n};"
},
"typeVersion": 2
},
{
"id": "5a9a3b2a-2b21-408c-a276-fb41812ab6de",
"name": "Analyze Transcript with Claude AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2592,
512
],
"parameters": {
"text": "=You are an expert meeting analyst and project coordinator. Analyze this meeting transcript and extract actionable information.\n\n**Meeting Details:**\n- Title: {{ $json.meeting.title }}\n- Date: {{ $json.meeting.date }}\n- Attendees: {{ $json.meeting.attendees.join(', ') }}\n- Duration: {{ Math.floor($json.transcript.durationSeconds / 60) }} minutes\n- Word Count: {{ $json.transcript.wordCount }}\n\n**Transcript:**\n{{ $json.transcript.fullText }}\n\n**Analysis Requirements:**\n\nExtract and categorize the following in JSON format:\n\n1. **Action Items** - Specific tasks that need to be completed\n - Task description (clear and actionable)\n - Assigned person (match to attendee list or extract from context)\n - Priority (high/medium/low based on urgency discussed)\n - Due date (if mentioned, otherwise null)\n - Context (relevant discussion that led to this task)\n\n2. **Decisions Made** - Key decisions and agreements (if extractDecisions: {{ $json.config.extractDecisions }})\n - Decision description\n - Decision maker\n - Impact level (high/medium/low)\n\n3. **Risks Identified** - Potential issues or blockers (if extractRisks: {{ $json.config.extractRisks }})\n - Risk description\n - Severity (high/medium/low)\n - Mentioned by whom\n\n4. **Next Steps** - Follow-up meetings or milestones\n - Description\n - Tentative date\n - Owners\n\n5. **Key Discussion Points** - Main topics covered\n - Topic\n - Summary\n - Time allocation (if determinable)\n\n**Response Format:**\n```json\n{\n \"actionItems\": [\n {\n \"title\": \"Clear, actionable task title\",\n \"description\": \"Detailed description with context\",\n \"assignee\": \"person@company.com or name\",\n \"priority\": \"high\",\n \"dueDate\": \"2024-01-20\",\n \"labels\": [\"engineering\", \"urgent\"],\n \"estimatedHours\": 4,\n \"dependencies\": [\"Other task if mentioned\"]\n }\n ],\n \"decisions\": [\n {\n \"decision\": \"Decision description\",\n \"decisionMaker\": \"Name\",\n \"impact\": \"high\",\n \"rationale\": \"Why this decision was made\"\n }\n ],\n \"risks\": [\n {\n \"risk\": \"Risk description\",\n \"severity\": \"medium\",\n \"mentionedBy\": \"Name\",\n \"mitigation\": \"Suggested mitigation if any\"\n }\n ],\n \"nextSteps\": [\n {\n \"description\": \"Follow-up action\",\n \"tentativeDate\": \"2024-01-25\",\n \"owner\": \"person@company.com\"\n }\n ],\n \"discussionPoints\": [\n {\n \"topic\": \"Topic name\",\n \"summary\": \"Brief summary\",\n \"timeSpent\": \"5 minutes\"\n }\n ],\n \"meetingSummary\": \"1-2 paragraph executive summary of the entire meeting\",\n \"attendeesPresent\": [\"List of people who actively participated\"],\n \"followUpRequired\": true\n}\n```\n\n**Important Guidelines:**\n- Match assignee names/emails to the attendees list when possible\n- If a task doesn't have a clear owner, set assignee to null\n- Be specific in task titles - avoid vague descriptions\n- Extract actual dates mentioned, or use null for due dates\n- Prioritize based on language cues (ASAP, urgent, critical, etc.)\n- Action items should be independently actionable\n- Include labels that help categorize work (engineering, design, marketing, etc.)",
"options": {
"systemMessage": "You are an expert meeting analyst who extracts structured, actionable information from meeting transcripts. Always respond in valid JSON format without markdown code blocks. Be precise and thorough."
},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "b89c5534-614f-43c6-b260-7938c3c3924c",
"name": "Claude AI Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
2832,
752
],
"parameters": {
"model": "=claude-sonnet-4-20250514",
"options": {
"temperature": 0.3
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "4dabe6b5-59fb-4323-85e7-f1f63bda7333",
"name": "Parse Analysis & Match Team Members",
"type": "n8n-nodes-base.code",
"position": [
2928,
512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Parse Claude's response\nconst aiResponse = $input.item.json;\nlet aiText = aiResponse.response || aiResponse.output || aiResponse.text || '';\n\n// Handle content array format\nif (aiResponse.content && Array.isArray(aiResponse.content)) {\n aiText = aiResponse.content[0]?.text || '';\n}\n\n// Clean JSON from markdown\nconst cleanText = aiText\n .replace(/```json\\s*/g, '')\n .replace(/```\\s*/g, '')\n .trim();\n\nlet analysis;\ntry {\n analysis = JSON.parse(cleanText);\n} catch (error) {\n throw new Error(`Failed to parse AI analysis: ${error.message}`);\n}\n\n// Get upstream data\nconst config = $('Validate Config & Build Params').item.json.config;\nconst teamMapping = $('Validate Config & Build Params').item.json.teamMapping;\nconst metadata = $('Validate Config & Build Params').item.json.metadata;\nconst transcript = $('Parse & Clean Transcript').item.json.transcript;\nconst meeting = $('Parse & Clean Transcript').item.json.meeting;\n\n// Match assignees to team member IDs\nconst actionItemsWithIds = (analysis.actionItems || []).map((item, index) => {\n let assigneeData = null;\n \n if (item.assignee) {\n // Try to match by email or name\n const assigneeLower = item.assignee.toLowerCase();\n \n for (const [email, userData] of Object.entries(teamMapping)) {\n if (\n email.toLowerCase() === assigneeLower ||\n userData.name.toLowerCase().includes(assigneeLower) ||\n assigneeLower.includes(userData.name.toLowerCase().split(' ')[0])\n ) {\n assigneeData = {\n email,\n ...userData\n };\n break;\n }\n }\n }\n \n return {\n ...item,\n id: `${metadata.workflowId}-task-${index + 1}`,\n assigneeData,\n createdFrom: 'meeting-automator'\n };\n});\n\nreturn {\n json: {\n analysis: {\n ...analysis,\n actionItems: actionItemsWithIds\n },\n meeting,\n transcript,\n config,\n teamMapping,\n metadata,\n stats: {\n totalActionItems: actionItemsWithIds.length,\n assignedItems: actionItemsWithIds.filter(i => i.assigneeData).length,\n unassignedItems: actionItemsWithIds.filter(i => !i.assigneeData).length,\n decisionsCount: (analysis.decisions || []).length,\n risksCount: (analysis.risks || []).length,\n highPriorityItems: actionItemsWithIds.filter(i => i.priority === 'high').length\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "d85ab826-8e80-4c31-8734-c64d658b0af6",
"name": "Route to Task Tool",
"type": "n8n-nodes-base.if",
"position": [
3168,
512
],
"parameters": {
"options": {},
"conditions": {
"options": {
"caseSensitive": false
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.config.taskTool }}",
"rightValue": "jira"
}
]
}
},
"typeVersion": 2
},
{
"id": "758fe82d-e50c-442a-9721-da5434a58e27",
"name": "Create Jira Task",
"type": "n8n-nodes-base.jira",
"position": [
3408,
416
],
"parameters": {
"project": {
"__rl": true,
"mode": "id",
"value": "=",
"__regex": "^([0-9]{2,})"
},
"summary": "={{ $item(0).$json.title }}",
"issueType": "Task",
"additionalFields": {
"labels": "={{ $item(0).$json.labels }}",
"assignee": "={{ $item(0).$json.assigneeData?.jiraAccountId }}",
"priority": "={{ $item(0).$json.priority.charAt(0).toUpperCase() + $item(0).$json.priority.slice(1) }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "602feaf5-44f9-4f9a-8713-05edc55c1db6",
"name": "Create ClickUp Task",
"type": "n8n-nodes-base.clickUp",
"position": [
3408,
608
],
"parameters": {
"list": "=",
"name": "={{ $item(0).$json.title }}",
"team": "=",
"space": "=",
"folder": "=",
"additionalFields": {
"tags": "={{ $item(0).$json.labels }}",
"dueDate": "={{ new Date($item(0).$json.dueDate).getTime() }}",
"priority": "={{ $item(0).$json.priority === 'high' ? 1 : $item(0).$json.priority === 'medium' ? 2 : 3 }}",
"assignees": "={{ [$item(0).$json.assigneeData?.clickupUserId] }}"
}
},
"credentials": {
"clickUpApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "178b379e-ff21-4afe-b3d0-4a5ab09f9074",
"name": "Merge Created Tasks",
"type": "n8n-nodes-base.merge",
"position": [
3648,
512
],
"parameters": {
"mode": "combine",
"options": {},
"mergeByFields": {
"values": [
{
"field1": "metadata.workflowId",
"field2": "metadata.workflowId"
}
]
}
},
"typeVersion": 2.1
},
{
"id": "96095213-55d7-4357-bbe3-6358841354da",
"name": "Send Slack Notification",
"type": "n8n-nodes-base.slack",
"position": [
3888,
416
],
"parameters": {
"text": "=\ud83c\udfaf New task assigned to you from meeting: **{{ $json.meeting.title }}**\\n\\n**Task:** {{ $item(0).$json.title }}\\n**Priority:** {{ $item(0).$json.priority }}\\n**Due:** {{ $item(0).$json.dueDate || 'No deadline' }}\\n\\n{{ $item(0).$json.description }}\\n\\n\ud83d\udccb View in {{ $json.config.taskTool }}: {{ $item(0).$json.taskUrl }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "=eerr4433rr"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2,
"continueOnFail": true
},
{
"id": "98304ca3-fa94-47c0-b82c-4c7b1f15e6f2",
"name": "Save Summary to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
3888,
608
],
"parameters": {
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": "={{ $json.config.driveFolderId || 'root' }}"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3,
"continueOnFail": true
},
{
"id": "9002635e-bb09-4e26-8ec6-7d82a87fd5f7",
"name": "Generate Summary Document",
"type": "n8n-nodes-base.code",
"position": [
3648,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Build comprehensive meeting summary document\nconst data = $input.item.json;\nconst analysis = data.analysis;\nconst meeting = data.meeting;\nconst stats = data.stats;\n\nconst markdown = `# ${meeting.title}\n**Date:** ${meeting.date}\n**Attendees:** ${meeting.attendees.join(', ')}\n**Duration:** ${Math.floor(data.transcript.durationSeconds / 60)} minutes\n\n---\n\n## Executive Summary\n${analysis.meetingSummary}\n\n## Statistics\n- Total Action Items: ${stats.totalActionItems}\n- High Priority Items: ${stats.highPriorityItems}\n- Decisions Made: ${stats.decisionsCount}\n- Risks Identified: ${stats.risksCount}\n\n---\n\n## Action Items (${stats.totalActionItems})\n\n${analysis.actionItems.map((item, i) => `\n### ${i + 1}. ${item.title}\n- **Assignee:** ${item.assigneeData?.name || item.assignee || 'Unassigned'}\n- **Priority:** ${item.priority.toUpperCase()}\n- **Due Date:** ${item.dueDate || 'Not specified'}\n- **Labels:** ${item.labels?.join(', ') || 'None'}\n- **Estimated Hours:** ${item.estimatedHours || 'TBD'}\n\n${item.description}\n\n**Context:** ${item.context || 'N/A'}\n`).join('\\n')}\n\n---\n\n## Decisions Made (${stats.decisionsCount})\n\n${(analysis.decisions || []).map((dec, i) => `\n${i + 1}. **${dec.decision}**\n - Decision Maker: ${dec.decisionMaker}\n - Impact: ${dec.impact.toUpperCase()}\n - Rationale: ${dec.rationale}\n`).join('\\n')}\n\n---\n\n## Risks & Blockers (${stats.risksCount})\n\n${(analysis.risks || []).map((risk, i) => `\n${i + 1}. **${risk.risk}**\n - Severity: ${risk.severity.toUpperCase()}\n - Mentioned By: ${risk.mentionedBy}\n - Mitigation: ${risk.mitigation || 'TBD'}\n`).join('\\n')}\n\n---\n\n## Key Discussion Points\n\n${(analysis.discussionPoints || []).map((point, i) => `\n### ${point.topic}\n${point.summary}\n*Time spent: ${point.timeSpent}*\n`).join('\\n')}\n\n---\n\n## Next Steps\n\n${(analysis.nextSteps || []).map((step, i) => `\n${i + 1}. ${step.description}\n - Owner: ${step.owner}\n - Tentative Date: ${step.tentativeDate || 'TBD'}\n`).join('\\n')}\n\n---\n\n## Full Transcript\n\n${data.transcript.fullText}\n\n---\n\n*Generated by Meeting Notes Automator*\n*Workflow ID: ${data.metadata.workflowId}*\n*Generated at: ${new Date().toISOString()}*\n`;\n\nreturn {\n json: {\n ...data,\n summaryDocument: markdown\n },\n binary: {\n data: {\n data: Buffer.from(markdown).toString('base64'),\n mimeType: 'text/markdown',\n fileName: `${meeting.title} - Meeting Notes - ${meeting.date}.md`\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "ed19032d-1269-4346-a64b-afb0ef9c9b45",
"name": "Format Final Response",
"type": "n8n-nodes-base.code",
"position": [
4128,
512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Build final response with all task links and summary\nconst allData = $input.all();\nconst mainData = allData[0].json;\nconst createdTasks = allData.slice(1).map(item => item.json);\n\nconst response = {\n success: true,\n message: `Successfully processed meeting and created ${mainData.stats.totalActionItems} action items`,\n meeting: {\n title: mainData.meeting.title,\n date: mainData.meeting.date,\n attendees: mainData.meeting.attendees,\n duration: `${Math.floor(mainData.transcript.durationSeconds / 60)} minutes`,\n wordCount: mainData.transcript.wordCount\n },\n summary: {\n executiveSummary: mainData.analysis.meetingSummary,\n totalActionItems: mainData.stats.totalActionItems,\n assignedItems: mainData.stats.assignedItems,\n unassignedItems: mainData.stats.unassignedItems,\n highPriorityItems: mainData.stats.highPriorityItems,\n decisionsCount: mainData.stats.decisionsCount,\n risksCount: mainData.stats.risksCount\n },\n actionItems: mainData.analysis.actionItems.map(item => ({\n id: item.id,\n title: item.title,\n assignee: item.assigneeData?.name || item.assignee || 'Unassigned',\n priority: item.priority,\n dueDate: item.dueDate,\n taskUrl: createdTasks.find(t => t.summary === item.title)?.url || '#',\n status: 'created'\n })),\n decisions: mainData.analysis.decisions || [],\n risks: mainData.analysis.risks || [],\n nextSteps: mainData.analysis.nextSteps || [],\n notifications: {\n sent: mainData.config.sendNotifications,\n recipientCount: mainData.stats.assignedItems\n },\n storage: {\n savedToDrive: mainData.config.saveToDrive,\n savedToNotion: mainData.config.saveToNotion,\n summaryUrl: '#' // Would be populated by Drive/Notion response\n },\n metadata: {\n workflowId: mainData.metadata.workflowId,\n processedAt: new Date().toISOString(),\n taskTool: mainData.config.taskTool\n }\n};\n\nreturn [{ json: response }];"
},
"typeVersion": 2
},
{
"id": "55ec249f-3f23-4ffe-93a2-5f020361741b",
"name": "Send Response to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4576,
512
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json, null, 2) }}"
},
"typeVersion": 1
},
{
"id": "d3589eed-ecac-4bc4-8a4e-48f332f27044",
"name": "Audio Extraction",
"type": "n8n-nodes-base.httpRequest",
"position": [
1952,
288
],
"parameters": {
"url": "={{ $json.config.Audio }}",
"options": {}
},
"typeVersion": 4.3
},
{
"id": "3edfba60-6b3d-4276-b7c5-f192ab7a3224",
"name": "Wait For Response Send",
"type": "n8n-nodes-base.wait",
"position": [
4336,
512
],
"parameters": {},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c35b7d37-df8a-41e3-b873-9948fcb0cd91",
"connections": {
"Claude AI Model": {
"ai_languageModel": [
[
{
"node": "Analyze Transcript with Claude AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Audio Extraction": {
"main": [
[
{
"node": "Wait for Transcription",
"type": "main",
"index": 0
}
]
]
},
"Create Jira Task": {
"main": [
[
{
"node": "Merge Created Tasks",
"type": "main",
"index": 0
}
]
]
},
"Detect File Type": {
"main": [
[
{
"node": "Transcribe Audio with Whisper",
"type": "main",
"index": 0
},
{
"node": "Audio Extraction",
"type": "main",
"index": 0
}
]
]
},
"Route to Task Tool": {
"main": [
[
{
"node": "Create Jira Task",
"type": "main",
"index": 0
}
],
[
{
"node": "Create ClickUp Task",
"type": "main",
"index": 0
}
]
]
},
"Create ClickUp Task": {
"main": [
[
{
"node": "Merge Created Tasks",
"type": "main",
"index": 1
}
]
]
},
"Merge Created Tasks": {
"main": [
[
{
"node": "Generate Summary Document",
"type": "main",
"index": 0
},
{
"node": "Send Slack Notification",
"type": "main",
"index": 0
}
]
]
},
"Check Meeting Source": {
"main": [
[
{
"node": "Download Zoom Recording",
"type": "main",
"index": 0
}
],
[
{
"node": "Download Google Meet Recording",
"type": "main",
"index": 0
}
]
]
},
"Format Final Response": {
"main": [
[
{
"node": "Wait For Response Send",
"type": "main",
"index": 0
}
]
]
},
"Wait For Response Send": {
"main": [
[
{
"node": "Send Response to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Wait for Transcription": {
"main": [
[
{
"node": "Parse & Clean Transcript",
"type": "main",
"index": 0
}
]
]
},
"Download Zoom Recording": {
"main": [
[
{
"node": "Detect File Type",
"type": "main",
"index": 0
}
]
]
},
"Send Slack Notification": {
"main": [
[
{
"node": "Format Final Response",
"type": "main",
"index": 0
}
]
]
},
"Parse & Clean Transcript": {
"main": [
[
{
"node": "Analyze Transcript with Claude AI",
"type": "main",
"index": 0
}
]
]
},
"Generate Summary Document": {
"main": [
[
{
"node": "Save Summary to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Save Summary to Google Drive": {
"main": [
[
{
"node": "Format Final Response",
"type": "main",
"index": 0
}
]
]
},
"Transcribe Audio with Whisper": {
"main": [
[
{
"node": "Wait for Transcription",
"type": "main",
"index": 0
}
]
]
},
"Download Google Meet Recording": {
"main": [
[
{
"node": "Detect File Type",
"type": "main",
"index": 0
}
]
]
},
"Validate Config & Build Params": {
"main": [
[
{
"node": "Check Meeting Source",
"type": "main",
"index": 0
}
]
]
},
"Analyze Transcript with Claude AI": {
"main": [
[
{
"node": "Parse Analysis & Match Team Members",
"type": "main",
"index": 0
}
]
]
},
"Receive Meeting Recording Webhook": {
"main": [
[
{
"node": "Validate Config & Build Params",
"type": "main",
"index": 0
}
]
]
},
"Parse Analysis & Match Team Members": {
"main": [
[
{
"node": "Route to Task Tool",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApiclickUpApigoogleDriveOAuth2ApigoogleOAuth2ApijiraSoftwareCloudApiopenAiApislackApizoomOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This AI-powered workflow transcribes Zoom/Google Meet recordings, extracts decisions and tasks using AI, then creates tickets in Jira/ClickUp/Linear and assigns them to team members automatically. Trigger - Receives meeting recording URL via webhook or schedule Download…
Source: https://n8n.io/workflows/15114/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
What if AI didn't just write content—but actually thought about how to write it? This n8n workflow revolutionizes content creation by deploying multiple specialized AI agents that handle every aspect
This workflow is an AI-powered virtual cinematography and previs generation pipeline designed for film and VFX production. It transforms a director’s shot description into multiple camera choreography
🧾 An intelligent automation system that turns Google Meet recordings into structured meeting notes — integrating Fireflies.ai, OpenAI GPT-4.1-mini, Notion, Slack, Google Drive, and Gmail via n8n.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
This workflow is for beauty salons who want consistent, high‑quality social media content without writing every post manually. It also suits agencies and automation builders who manage multiple beauty