Every meeting produces three or four action items, and roughly half of them die in someone’s notebook. The fix isn’t another note-taking app — it’s a pipeline: transcript comes in, AI pulls out the action items with an owner and a due date for each, tasks land in Asana (or Trello, or ClickUp), and a summary hits Slack before anyone has left the call’s mental context.
This tutorial builds that pipeline in Make, with a shorter Zapier variant at the end. By the end you’ll have a scenario that runs unattended on every recorded meeting. Total build time is about 40 minutes if your accounts are already connected.
What you’re building
The flow has five stages:
- Trigger — a new transcript arrives. Fireflies and Zoom can push via webhook; Google Meet transcripts land as Google Docs in a Drive folder, so we watch that folder instead.
- AI extraction — an OpenAI module reads the transcript and returns a strict JSON array of action items: task title, owner, due date, and a one-line context note.
- Iterator — Make splits that array so each action item becomes its own bundle.
- Task creation — one Asana task per action item, assigned and dated.
- Slack summary — a single message to your team channel with the meeting recap and a count of created tasks.
If you’d rather import than build, grab the template and skim the steps to see which fields need your own connection IDs:
Free template · make
Meeting Notes to Tasks
meeting-notes-to-tasks-make.json
Prerequisites
- A Make account (the free tier’s 1,000 operations/month covers roughly 150–200 meetings on this scenario; as of mid-2026, check current pricing)
- An OpenAI API key with a few dollars of credit
- Asana, Slack, and either Fireflies, Zoom, or Google Meet with transcription turned on
- Owner names in your transcripts that map to real Asana users (more on this in step 4)
Step 1: Set up the trigger
You have two paths depending on your meeting tool.
Path A: Webhook (Fireflies, Zoom)
- In Make, create a new scenario and add Webhooks > Custom webhook as the first module. Click Add, name it
meeting-transcripts, and copy the URL. - In Fireflies: Settings > Developer Settings > Webhooks, paste the URL. Fireflies sends a
meetingIdon completion, so add a second module: HTTP > Make a request tohttps://api.fireflies.ai/graphqlwith your Fireflies API key as a Bearer token, posting a GraphQL query for the transcript sentences. (The downloadable template includes this request pre-built.) - In Zoom: create a Webhook-Only app in the Zoom Marketplace, subscribe to the Recording transcript files completed event, and point it at the Make URL. You’ll then fetch the VTT file with an HTTP > Get a file module using the
download_tokenZoom includes in the payload.
Path B: Drive folder (Google Meet)
- Add Google Drive > Watch Files in a Folder as the trigger. Set Folder to
Meet Recordings(Google creates this automatically), Watch toCreated files. - Add Google Docs > Get Content of a Document, mapping the file ID from the trigger. The plain-text output is your transcript.
Either way, by the end of this step you have one variable containing the full transcript text. Run the scenario once with a real meeting to confirm before moving on — debugging mapping issues is far easier with live data in the execution log.
Step 2: Extract action items with OpenAI
- Add OpenAI > Create a Completion (the module labeled “Create a chat completion” on newer Make versions).
- Model:
gpt-4o-miniis plenty for extraction and keeps costs near-zero. Save the bigger models for generation tasks. - Response format: set to JSON object. This is the single most important setting in the whole scenario — without it the model will occasionally wrap its answer in markdown fences and break your iterator.
- Max tokens: 2000. Temperature: 0.2 — extraction should be boring and deterministic.
- Add a System role message and a User role message with the prompt below, mapping your transcript variable where indicated.
SYSTEM:
You are an action-item extraction engine. You read meeting transcripts and return ONLY valid JSON, no prose, no markdown fences.
USER:
Extract every action item from this meeting transcript.
Rules:
- An action item is a concrete commitment to do something, not a general discussion topic.
- "owner" is the first name of the person who committed to the task. If genuinely unclear, use "UNASSIGNED".
- "due_date" must be ISO format (YYYY-MM-DD). Resolve relative dates ("by Friday", "next week") against the meeting date: {{meeting_date}}. If no date was mentioned, use null.
- "context" is one sentence quoting or paraphrasing why this task exists.
- If there are no action items, return {"items": [], "summary": "..."}.
Return exactly this JSON shape:
{
"summary": "2-3 sentence meeting summary",
"items": [
{"title": "...", "owner": "...", "due_date": "YYYY-MM-DD or null", "context": "..."}
]
}
TRANSCRIPT:
{{transcript_text}}
Note the {{meeting_date}} placeholder — map the meeting’s date from your trigger (or use Make’s formatDate(now; "YYYY-MM-DD") if the trigger doesn’t supply one). Without an anchor date, “by Friday” is meaningless to the model and you’ll get hallucinated dates.
Step 3: Parse and iterate
- Add JSON > Parse JSON after the OpenAI module. Map the Result (choices[0].message.content) into it. Click Generate data structure and paste a sample of the JSON shape above so Make learns the schema.
- Add Flow Control > Iterator and map the
itemsarray from the Parse JSON output. Each action item now flows through the rest of the scenario as its own bundle. - Right after the iterator, add a Filter (click the wrench on the connection line): condition
ownernot equal toUNASSIGNED. Unassigned items shouldn’t silently become tasks nobody owns — we’ll surface them in the Slack summary instead.
Step 4: Create the tasks in Asana
- Add Asana > Create a Task.
- Workspace and Project: pick your team’s project.
- Name: map
titlefrom the iterator. - Notes: map
context, plus a line likeFrom meeting on {{meeting_date}} — auto-created. Future-you will want to know where tasks came from. - Due on: map
due_date. Asana accepts ISO dates directly, which is why the prompt enforces that format. - Assignee: this is the one fiddly part. The AI gives you a first name (“Maria”); Asana wants a user ID. The clean fix is a Tools > Set Variable module before this one containing a lookup built with Make’s
switch()function:
{{switch(lower(2.owner); "maria"; "1199…882"; "james"; "1199…417"; "priya"; "1200…003"; "UNMATCHED")}}
Map that variable into Assignee, and add a filter so UNMATCHED rows skip assignment rather than erroring. A five-person team needs five entries; maintain it in one place and forget it.
For Trello, swap in Trello > Create a Card (map title to Name, context to Description, due_date to Due). For ClickUp, use ClickUp > Create a Task — it accepts an assignee email, which makes the lookup table map names to emails instead of IDs, slightly easier to maintain.
Step 5: Post the summary to Slack
After the iterator branch completes, you want one Slack message, not one per task. Add Slack > Create a Message on a separate route before the iterator (routes run in order, top to bottom), or use a Tools > Text Aggregator after the Asana module feeding into Slack. The aggregator approach lets you list the created tasks:
- Add Flow Control > Text Aggregator, source module = the Iterator, text:
• {{title}} → {{owner}} (due {{due_date}}). - Add Slack > Create a Message. Channel: your team channel. Text:
:clipboard: *Meeting recap — {{meeting_date}}*
{{summary}}
*Action items created in Asana:*
{{aggregated_text}}
Run the full scenario end-to-end with a real transcript. Check the Asana tasks, check the dates resolved correctly, check Slack. Then turn scheduling On.
Try it yourself
Make
The iterator + aggregator pattern in this build is exactly where Make's visual model beats a linear Zap.
Start with MakeThe Zapier variant
If your team already lives in Zapier, the same flow works with two compromises: looping requires the Looping by Zapier built-in (clunkier than Make’s iterator), and you’ll pay per task created since each loop iteration consumes Zapier tasks.
- Trigger: Fireflies (native Zapier app) → “Transcript Ready”, or Google Drive → “New File in Folder”.
- Action: ChatGPT (OpenAI) → “Conversation”, same prompt as above. Zapier’s OpenAI integration doesn’t expose a JSON response-format toggle on all plans, so append “Do not wrap the JSON in markdown fences” to the system prompt and add a Formatter > Text > Replace step stripping ``` just in case.
- Code by Zapier (Run Javascript):
return JSON.parse(inputData.raw).items;— this parses and hands an array to the next step. - Looping by Zapier: create loop from line items.
- Inside the loop: Asana > Create Task. After the loop: Slack > Send Channel Message.
Five-plus steps with looping puts you on a paid Zapier plan (as of mid-2026, check current pricing). For this specific workload Make is meaningfully cheaper because an entire meeting is one scenario run, not eight tasks.
Which platform should you use?
- Make — the right default here. Native iterators, JSON parsing, and aggregation are exactly what transcript → many-tasks needs, and per-operation pricing favors bursty meeting traffic.
- Zapier — pick it if Fireflies/Zoom/Asana are already connected in your account and you value the 10-minute setup over the per-task cost. The native Fireflies trigger is genuinely nicer than wiring a webhook.
- n8n — overkill for this beginner build, but the right answer once you want self-hosting, local LLMs for transcript privacy, or custom dedupe logic in code. See our full Make vs Zapier vs n8n comparison for the long version.
Common errors and fixes
The iterator fails with “not an array.” The model returned prose or fenced JSON instead of the raw object. Confirm Response format: JSON object is set on the OpenAI module — it’s cleared if you switch models. The fenced-output failure mode mostly disappears once that flag is on.
Asana returns 401 or “Not authorized” on task creation. Your Asana connection was authorized by a user who isn’t a member of the target project. Asana scopes access by project membership, not workspace. Re-add the connecting user to the project, or reconnect as someone who’s in it.
Zoom webhook validation fails. Zoom requires you to answer its endpoint.url_validation challenge before it will deliver events. Add a router after the webhook: one branch checks event = endpoint.url_validation and replies with the hashed token via Webhook Response; the other branch handles real events. The template includes this.
Due dates are wrong by a week. You forgot to map {{meeting_date}} into the prompt, so the model resolved “next Friday” against its own unknown “today.” Always anchor relative dates.
OpenAI returns 429 rate-limit errors on busy days. New OpenAI accounts have low requests-per-minute limits. In the Make module’s error handler, add a Break directive with 3 retries and a 60-second interval — meeting processing isn’t latency-sensitive, so just wait it out.
Slack message posts but tasks are missing. Check the filter after the iterator: if every owner came back UNASSIGNED, the model couldn’t attribute commitments. This usually means the transcript has no speaker labels (common with raw VTT from Zoom when speaker identification is off). Turn on speaker labeling in your meeting tool.
Where to take it next
The same extract-iterate-create skeleton powers most “document in, structured records out” automations. If this clicked, the natural next builds are AI email triage, which applies the same JSON-extraction prompt pattern to your inbox, and automated weekly reports, which aggregates what this scenario creates back into a Friday summary. And if your meetings are sales calls, route the extracted items into your CRM with the lead capture tutorial instead of Asana.
One scenario, forty minutes of setup, and action items stop evaporating. That’s the trade.