AutomationFlowsAI & RAG › Extract Meeting Action Items and Decisions with Openai, Asana and Sheets

Extract Meeting Action Items and Decisions with Openai, Asana and Sheets

ByOneclick AI Squad @oneclick-ai on n8n.io

This workflow ingests meeting audio or transcripts via a webhook or a 30-minute schedule, uses OpenAI (Whisper and GPT-4.1-mini) to extract summaries, decisions, and action items, creates tasks in Asana, logs each meeting to Google Sheets, and emails a recap through SendGrid.…

Webhook trigger★★★★☆ complexityAI-powered23 nodesHTTP RequestAgentOpenAI Chat
AI & RAG Trigger: Webhook Nodes: 23 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16227 — we link there as the canonical source.

This workflow follows the Agent → HTTP Request 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 →

Download .json
{
  "id": "OtXp4PWNEIru8oPS",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Extract action items and decisions from meeting transcripts into Asana and Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "3150c233-acf4-4823-8332-1f6c1cd8f47c",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        -272
      ],
      "parameters": {
        "width": 980,
        "height": 1020,
        "content": "## Extract action items and decisions from meeting transcripts\n\nThis workflow converts meeting recordings or transcripts into structured intelligence: full transcripts, key decisions, action items, and automatic task syncing to your project management tool.\n\n### Who's it for\n\u2022 Teams running 5+ meetings per week\n\u2022 Project managers needing automatic task capture\n\u2022 Executives wanting searchable meeting records\n\u2022 Remote teams requiring async meeting summaries\n\n### How it works / What it does\n1. Accepts meeting input via webhook (upload or calendar event)\n2. Polls for scheduled meetings every 30 minutes\n3. Validates and normalises the incoming meeting data\n4. Transcribes audio/video or ingests existing transcript\n5. AI extracts decisions, action items, and a structured summary\n6. Formats the intelligence into a clean output object\n7. Pushes action items to your project management tool (e.g. Asana / Linear)\n8. Logs every meeting and its outputs to Google Sheets\n9. Sends a summary email/Slack notification to participants\n\n### How to set up\n1. Import this workflow into n8n\n2. Configure credentials: Webhook secret, OpenAI API, Google Sheets OAuth, PM tool API, SendGrid\n3. Replace placeholder IDs (YOUR_SHEET_ID, YOUR_PROJECT_ID) with real values\n4. Update the resume/preferences fields in the AI node with your team context\n5. Activate the workflow\n\n### Requirements\n\u2022 n8n instance (cloud or self-hosted)\n\u2022 OpenAI API key (Whisper + GPT-4.1-mini)\n\u2022 Google Sheets with OAuth2\n\u2022 Asana / Linear / Jira API key (or swap the HTTP node)\n\u2022 SendGrid API key (or swap for Gmail node)\n\n### How to customise\n\u2022 Swap GPT-4.1-mini for Claude Sonnet in the LLM node\n\u2022 Change extraction prompt to match your meeting taxonomy\n\u2022 Add a Slack node after the email send step\n\u2022 Extend the Google Sheet columns for custom metadata\n\u2022 Replace Asana with any HTTP-compatible task tool"
      },
      "typeVersion": 1
    },
    {
      "id": "2a6bee86-b7a4-4ddd-a887-3f40fbbcab87",
      "name": "Sticky Note - Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        -32
      ],
      "parameters": {
        "color": 3,
        "width": 780,
        "height": 520,
        "content": "## 1. Trigger & Intake\nAccepts meeting data from webhook POST or\nscheduled poll. Normalises all fields into\na consistent context object."
      },
      "typeVersion": 1
    },
    {
      "id": "ae20767d-b2bb-4d9b-86a8-66a433860592",
      "name": "Sticky Note - Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        -128
      ],
      "parameters": {
        "color": 3,
        "width": 780,
        "height": 520,
        "content": "## 2. Transcription & Validation\nValidates that audio/transcript is present,\ncalls Whisper if audio URL provided,\nthen filters out non-meeting payloads."
      },
      "typeVersion": 1
    },
    {
      "id": "8dc99b22-6c53-4635-9f40-59f6e3d08f63",
      "name": "Sticky Note - Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2880,
        -128
      ],
      "parameters": {
        "color": 3,
        "width": 828,
        "height": 760,
        "content": "## 3. AI Analysis & Extraction\nSends transcript to GPT-4.1-mini with a\nstructured prompt. Extracts decisions,\naction items, summary, and owners."
      },
      "typeVersion": 1
    },
    {
      "id": "8462169a-2c71-4f40-8a05-337eeb30f93a",
      "name": "Sticky Note - Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3760,
        -96
      ],
      "parameters": {
        "color": 3,
        "width": 1364,
        "height": 792,
        "content": "## 4. Output, Sync & Notify\nFormats the structured output, pushes action\nitems to the PM tool, logs to Sheets, and\nsends a summary notification."
      },
      "typeVersion": 1
    },
    {
      "id": "9c8dd843-ad8a-4628-93a0-25320a65b5b4",
      "name": "Webhook - New Meeting Upload",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1360,
        112
      ],
      "parameters": {
        "path": "meeting-intelligence-inbound",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "1102cce2-2700-40a4-9250-3f30da30bec2",
      "name": "Poll Scheduled Meetings",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1360,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/30 * * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3391a76f-141c-443f-a8a9-327388876d35",
      "name": "Normalise Meeting Context",
      "type": "n8n-nodes-base.set",
      "position": [
        1600,
        208
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "meetingId",
              "type": "string",
              "value": "={{ $json.meetingId || $json.body?.meetingId || 'mtg-' + Date.now().toString() }}"
            },
            {
              "name": "meetingTitle",
              "type": "string",
              "value": "={{ $json.title || $json.body?.title || 'Untitled Meeting' }}"
            },
            {
              "name": "meetingDate",
              "type": "string",
              "value": "={{ $json.date || $json.body?.date || new Date().toISOString().split('T')[0] }}"
            },
            {
              "name": "participants",
              "type": "string",
              "value": "={{ $json.participants || $json.body?.participants || '' }}"
            },
            {
              "name": "audioUrl",
              "type": "string",
              "value": "={{ $json.audioUrl || $json.body?.audioUrl || '' }}"
            },
            {
              "name": "rawTranscript",
              "type": "string",
              "value": "={{ $json.transcript || $json.body?.transcript || '' }}"
            },
            {
              "name": "platform",
              "type": "string",
              "value": "={{ $json.platform || $json.body?.platform || 'unknown' }}"
            },
            {
              "name": "durationMinutes",
              "type": "number",
              "value": "={{ $json.durationMinutes || $json.body?.durationMinutes || 0 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5a57b2a5-3fa5-4e61-ae2f-2f4abded01f1",
      "name": "Python - Validate & Route",
      "type": "n8n-nodes-base.code",
      "notes": "Checks that the payload contains either an audioUrl or a rawTranscript. Sets hasAudio and hasTranscript flags, and marks invalid payloads so the filter can drop them.\n\n# --- paste this into the Code node body ---\nitem = _input.item.json\naudio_url = item.get('audioUrl', '').strip()\nraw_tx = item.get('rawTranscript', '').strip()\nhas_audio = len(audio_url) > 10\nhas_transcript = len(raw_tx) > 50\nitem['hasAudio'] = has_audio\nitem['hasTranscript'] = has_transcript\nitem['isValidMeeting'] = has_audio or has_transcript\nitem['needsTranscription'] = has_audio and not has_transcript\nreturn {'json': item}",
      "position": [
        1840,
        208
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "pythonNative",
        "pythonCode": "item = _input.item.json\n\naudio_url    = (item.get('audioUrl', '')    or '').strip()\nraw_tx       = (item.get('rawTranscript', '') or '').strip()\n\nhas_audio      = len(audio_url) > 10\nhas_transcript = len(raw_tx) > 50\n\nitem['hasAudio']          = has_audio\nitem['hasTranscript']     = has_transcript\nitem['isValidMeeting']    = has_audio or has_transcript\nitem['needsTranscription'] = has_audio and not has_transcript\n\n# Basic metadata validation\nmeeting_title = (item.get('meetingTitle', '') or '').strip()\nmeeting_date  = (item.get('meetingDate',  '') or '').strip()\n\nitem['hasMeetingTitle'] = len(meeting_title) > 0\nitem['hasMeetingDate']  = len(meeting_date)  > 0\n\n# Participant count (comma-separated string or list)\nparticipants = item.get('participants', '')\nif isinstance(participants, list):\n    item['participantCount'] = len(participants)\nelif isinstance(participants, str) and len(participants) > 0:\n    item['participantCount'] = len([p.strip() for p in participants.split(',') if p.strip()])\nelse:\n    item['participantCount'] = 0\n\n# Transcript word count estimate (useful for AI token planning)\nif has_transcript:\n    item['transcriptWordCount'] = len(raw_tx.split())\nelse:\n    item['transcriptWordCount'] = 0\n\n# Validation reason (helpful for debugging dropped items)\nif not item['isValidMeeting']:\n    if not has_audio and not has_transcript:\n        item['invalidReason'] = 'No audioUrl or rawTranscript provided'\n    elif has_audio and len(audio_url) <= 10:\n        item['invalidReason'] = 'audioUrl too short to be valid'\n    else:\n        item['invalidReason'] = 'rawTranscript too short (minimum 50 characters)'\nelse:\n    item['invalidReason'] = None\n\nreturn {'json': item}"
      },
      "typeVersion": 2
    },
    {
      "id": "08f94761-7e9a-40ce-8687-cd10eed9dddf",
      "name": "Filter Valid Meetings",
      "type": "n8n-nodes-base.filter",
      "position": [
        2080,
        208
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.isValidMeeting }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "df6fbe52-476c-4431-bb8c-add40f215321",
      "name": "Route - Audio or Text",
      "type": "n8n-nodes-base.filter",
      "position": [
        2320,
        208
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.needsTranscription }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "06b8d626-1565-4b96-a99d-446ee509704d",
      "name": "Whisper - Transcribe Audio",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2576,
        96
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/audio/transcriptions",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "whisper-1"
            },
            {
              "name": "url",
              "value": "={{ $json.audioUrl }}"
            },
            {
              "name": "response_format",
              "value": "text"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.openAiApi.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7f5b7020-e576-4247-bb19-008d9a64cd02",
      "name": "Merge Transcript into Context",
      "type": "n8n-nodes-base.set",
      "position": [
        2928,
        208
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "rawTranscript",
              "type": "string",
              "value": "={{ $json.text || $json.rawTranscript }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "66436a69-1c1f-48c6-8416-9bf7d7907a34",
      "name": "AI - Extract Meeting Intelligence",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3184,
        208
      ],
      "parameters": {
        "text": "=You are an expert meeting analyst. Analyse the following meeting transcript and return a structured JSON object.\n\nMeeting Metadata:\n- Title: {{ $json.meetingTitle }}\n- Date: {{ $json.meetingDate }}\n- Participants: {{ $json.participants }}\n- Duration: {{ $json.durationMinutes }} minutes\n- Platform: {{ $json.platform }}\n\nTranscript:\n{{ $json.rawTranscript }}\n\nReturn ONLY valid JSON with this exact schema (no markdown, no preamble):\n{\n  \"summary\": \"<2-4 sentence executive summary>\",\n  \"keyTopics\": [\"<topic 1>\", \"<topic 2>\"],\n  \"decisions\": [\n    {\n      \"decision\": \"<what was decided>\",\n      \"rationale\": \"<brief reason>\",\n      \"decidedBy\": \"<name or team>\"\n    }\n  ],\n  \"actionItems\": [\n    {\n      \"task\": \"<task description>\",\n      \"owner\": \"<person responsible>\",\n      \"dueDate\": \"<YYYY-MM-DD or null>\",\n      \"priority\": \"<high|medium|low>\"\n    }\n  ],\n  \"risks\": [\"<risk 1>\", \"<risk 2>\"],\n  \"followUpDate\": \"<YYYY-MM-DD or null>\"\n}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "a0b8a96d-ea88-4fee-932b-39dd00b46493",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        3200,
        416
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "890065fc-ef9e-4360-87e1-7ea54c8aa334",
      "name": "JS - Parse & Structure Output",
      "type": "n8n-nodes-base.code",
      "position": [
        3568,
        208
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Safely parse the AI response\nlet intelligence = {};\ntry {\n  const raw = item.response || item.output || item.text || '{}';\n  const clean = raw.replace(/```json|```/g, '').trim();\n  intelligence = JSON.parse(clean);\n} catch (e) {\n  intelligence = {\n    summary: item.response || 'Parsing failed \u2014 see raw output.',\n    keyTopics: [],\n    decisions: [],\n    actionItems: [],\n    risks: [],\n    followUpDate: null\n  };\n}\n\nreturn {\n  json: {\n    // Pass through original meeting metadata\n    meetingId: item.meetingId,\n    meetingTitle: item.meetingTitle,\n    meetingDate: item.meetingDate,\n    participants: item.participants,\n    platform: item.platform,\n    durationMinutes: item.durationMinutes,\n    rawTranscript: item.rawTranscript,\n\n    // Structured intelligence\n    summary: intelligence.summary || '',\n    keyTopics: intelligence.keyTopics || [],\n    decisions: intelligence.decisions || [],\n    actionItems: intelligence.actionItems || [],\n    risks: intelligence.risks || [],\n    followUpDate: intelligence.followUpDate || null,\n\n    // Convenience fields for downstream nodes\n    actionItemCount: (intelligence.actionItems || []).length,\n    decisionCount: (intelligence.decisions || []).length,\n    processedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "d850bfa1-47e3-4d38-b516-3e91c9347fe4",
      "name": "JS - Split Action Items",
      "type": "n8n-nodes-base.code",
      "position": [
        4048,
        112
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Expand action items into individual items for the PM sync loop\nconst item = $input.item.json;\nconst actionItems = item.actionItems || [];\n\nif (actionItems.length === 0) {\n  return [{ json: { ...item, _noActionItems: true } }];\n}\n\nreturn actionItems.map(ai => ({\n  json: {\n    ...item,\n    currentActionItem: ai,\n    taskTitle: ai.task || 'Untitled task',\n    taskOwner: ai.owner || 'Unassigned',\n    taskDueDate: ai.dueDate || null,\n    taskPriority: ai.priority || 'medium',\n    taskNotes: `From meeting: ${item.meetingTitle} (${item.meetingDate})`\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "7f4fbae8-5128-4276-9a44-bcb42b7433d3",
      "name": "Filter Has Action Items",
      "type": "n8n-nodes-base.filter",
      "position": [
        4288,
        112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "false"
              },
              "leftValue": "={{ $json._noActionItems }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0ffb0f9e-3506-4729-95fe-8ad5f2465cd6",
      "name": "Asana - Create Task",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4528,
        112
      ],
      "parameters": {
        "url": "https://app.asana.com/api/1.0/tasks",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "jsonBody": "={\n  \"data\": {\n    \"name\": \"{{ $json.taskTitle }}\",\n    \"notes\": \"{{ $json.taskNotes }}\",\n    \"assignee\": \"{{ $json.taskOwner }}\",\n    \"due_on\": \"{{ $json.taskDueDate }}\",\n    \"projects\": [\"YOUR_PROJECT_ID\"]\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer YOUR_ASANA_PAT"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8d1fc55f-0492-4344-95b3-2c76f1fad1e5",
      "name": "Google Sheets - Log Meeting",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4528,
        320
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/MeetingLog!A1:append?valueInputOption=USER_ENTERED",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"values\": [[\n    \"{{ $json.meetingDate }}\",\n    \"{{ $json.meetingTitle }}\",\n    \"{{ $json.participants }}\",\n    \"{{ $json.platform }}\",\n    \"{{ $json.durationMinutes }}\",\n    \"{{ $json.summary }}\",\n    \"{{ $json.decisionCount }}\",\n    \"{{ $json.actionItemCount }}\",\n    \"{{ $json.followUpDate || '' }}\",\n    \"{{ $json.processedAt }}\"\n  ]]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.googleSheetsOAuth2Api.accessToken }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "cd5c7591-b11e-4d27-a440-c7e2bd2a5a0d",
      "name": "Send Summary Notification",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4528,
        512
      ],
      "parameters": {
        "url": "https://api.sendgrid.com/v3/mail/send",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"personalizations\": [{\n    \"to\": [{ \"email\": \"your-team@company.com\" }]\n  }],\n  \"from\": { \"email\": \"meetings@company.com\", \"name\": \"Meeting Intelligence Hub\" },\n  \"subject\": \"Meeting Summary: {{ $json.meetingTitle }} ({{ $json.meetingDate }})\",\n  \"content\": [{\n    \"type\": \"text/plain\",\n    \"value\": \"MEETING SUMMARY\\n\\nTitle: {{ $json.meetingTitle }}\\nDate: {{ $json.meetingDate }}\\nParticipants: {{ $json.participants }}\\nDuration: {{ $json.durationMinutes }} minutes\\n\\nSUMMARY\\n{{ $json.summary }}\\n\\nKEY DECISIONS ({{ $json.decisionCount }})\\n{{ JSON.stringify($json.decisions, null, 2) }}\\n\\nACTION ITEMS ({{ $json.actionItemCount }})\\n{{ JSON.stringify($json.actionItems, null, 2) }}\\n\\nRISKS\\n{{ JSON.stringify($json.risks, null, 2) }}\\n\\nFollow-up Date: {{ $json.followUpDate || 'Not scheduled' }}\\n\\n-- Meeting Intelligence Hub\"\n  }]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer YOUR_SENDGRID_API_KEY"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "bfbbaf79-e5d3-4b60-960a-37fa8fb1997f",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        4800,
        320
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, meetingId: $json.meetingId, actionItems: $json.actionItemCount, decisions: $json.decisionCount, processedAt: $json.processedAt }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "ec6e3670-0450-4216-81df-1f1c772b04cf",
      "name": "Wait - Review Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        3808,
        208
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "eaeb0a96-4f1b-44ba-9daa-a4023ab7fab6",
  "connections": {
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Extract Meeting Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Asana - Create Task": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Review Buffer": {
      "main": [
        [
          {
            "node": "JS - Split Action Items",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Sheets - Log Meeting",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Summary Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Meetings": {
      "main": [
        [
          {
            "node": "Route - Audio or Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route - Audio or Text": {
      "main": [
        [
          {
            "node": "Whisper - Transcribe Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Has Action Items": {
      "main": [
        [
          {
            "node": "Asana - Create Task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Split Action Items": {
      "main": [
        [
          {
            "node": "Filter Has Action Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Scheduled Meetings": {
      "main": [
        [
          {
            "node": "Normalise Meeting Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalise Meeting Context": {
      "main": [
        [
          {
            "node": "Python - Validate & Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Validate & Route": {
      "main": [
        [
          {
            "node": "Filter Valid Meetings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Summary Notification": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Whisper - Transcribe Audio": {
      "main": [
        [
          {
            "node": "Merge Transcript into Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets - Log Meeting": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - New Meeting Upload": {
      "main": [
        [
          {
            "node": "Normalise Meeting Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Parse & Structure Output": {
      "main": [
        [
          {
            "node": "Wait - Review Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Transcript into Context": {
      "main": [
        [
          {
            "node": "AI - Extract Meeting Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Extract Meeting Intelligence": {
      "main": [
        [
          {
            "node": "JS - Parse & Structure Output",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow ingests meeting audio or transcripts via a webhook or a 30-minute schedule, uses OpenAI (Whisper and GPT-4.1-mini) to extract summaries, decisions, and action items, creates tasks in Asana, logs each meeting to Google Sheets, and emails a recap through SendGrid.…

Source: https://n8n.io/workflows/16227/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.

HTTP Request, Agent, Google Calendar Tool +9
AI & RAG

CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.

Postgres, Mcp Client Tool, Google Drive Tool +14
AI & RAG

Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.

OpenAI Chat, Memory Postgres Chat, OpenAI +7
AI & RAG

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

Google Sheets, HTTP Request, Slack +10