This workflow corresponds to n8n.io template #15095 — we link there as the canonical source.
This workflow follows the Agent → Gmail 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "3157608f-37ee-48a0-9670-d40e281d9424",
"name": "Sticky Note \u2014 Intro",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
2240
],
"parameters": {
"color": 4,
"width": 500,
"height": 1100,
"content": "## Generate post-meeting surveys from Google Meet notes with AI and Weavely\n\n\n\n@[youtube](xapyKQZQm7Q)\n\n\n## Who is it for\n\nTeams and individuals who run recurring meetings and want structured feedback without the manual effort of writing and sending surveys after every call.\n\n## How it works\n\n1. Google Calendar detects when a meeting ends\n2. Waits 10 minutes for Gemini to attach notes to the event\n3. Reads the Gemini notes doc from Google Drive\n4. An AI agent generates 5\u20137 tailored survey questions based on the actual discussion\n5. Weavely MCP builds and publishes the form automatically\n6. A Slack message is sent to the organiser to review and approve\n7. On approval, all attendees receive a branded email with the survey link\n\n## How to set up\n\n1. Update the **\u2699\ufe0f Configuration** node with your calendar email and Slack user ID\n2. Connect **Google Calendar**, **Google Docs**, **Gmail**, **OpenAI**, and **Slack** credentials\n3. Enable Interactivity in your Slack app and set the request URL to your n8n webhook-waiting URL\n\n## Requirements\n\n- Google Workspace Business Standard or higher (for Gemini meeting notes)\n- OpenAI API key\n- Weavely account \u2014 free at [weavely.ai](https://weavely.ai)\n- Slack workspace and Gmail account\n\n## How to customise\n\n- Swap Google Calendar for Calendly or any other calendar trigger\n- Replace Gmail with a Slack message to the meeting channel\n- Swap OpenAI for any other LLM supported by n8n\n- Adjust the AI prompt to change the number or style of survey questions"
},
"typeVersion": 1
},
{
"id": "sticky-config",
"name": "Sticky Note \u2014 Config",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
3380
],
"parameters": {
"color": 3,
"width": 380,
"height": 200,
"content": "## \u2699\ufe0f Configure here\nUpdate these two values before activating the workflow:\n- `calendarId` \u2192 your Google Calendar email address\n- `slackUserId` \u2192 your Slack member ID (Slack profile \u2192 \u00b7\u00b7\u00b7 \u2192 Copy member ID)"
},
"typeVersion": 1
},
{
"id": "a17256f7-fbf5-40f2-ad69-ec90ac99d01a",
"name": "Sticky Note \u2014 Section 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
4128,
2544
],
"parameters": {
"color": 7,
"width": 560,
"height": 340,
"content": "## 1. Trigger + wait\nFires every minute, detects when a meeting matching 'Meeting' has ended, then waits 10 minutes to ensure Gemini has generated and attached the notes doc to the event before re-fetching it."
},
"typeVersion": 1
},
{
"id": "b880c2a2-eb08-4be1-9523-4c59b9acf2d9",
"name": "Sticky Note \u2014 Section 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4720,
2544
],
"parameters": {
"color": 7,
"width": 540,
"height": 340,
"content": "## 2. Extract Gemini notes\nLooks for an attachment titled 'Notes by Gemini' on the calendar event, then reads the Google Doc content.\n\n**Note:** Requires Google Workspace Business Standard or higher for Gemini notes to be generated automatically."
},
"typeVersion": 1
},
{
"id": "cfb36b9c-99a7-434c-b00c-1e085ce9189a",
"name": "Sticky Note \u2014 Section 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
5328,
2544
],
"parameters": {
"color": 7,
"width": 680,
"height": 512,
"content": "## 3. AI agent + Weavely MCP\nThe agent reads the meeting notes and generates 5\u20137 tailored survey questions, then uses the Weavely MCP tools to build and publish the form. A user-owned copy of the form is then created via the Weavely API, returning the share URL and a claim link.\n\n\u26a0\ufe0f Swap `gpt-4.1-mini` for `gpt-4o` if you want higher quality output."
},
"typeVersion": 1
},
{
"id": "fb0a3510-0c11-4666-991c-22e87b508f47",
"name": "Sticky Note \u2014 Section 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
6080,
2544
],
"parameters": {
"color": 7,
"width": 560,
"height": 404,
"content": "## 4. Slack approval\nSends a Slack DM to the organiser with the Weavely claim link and Approve / Reject buttons. The workflow pauses here until a response is received.\n\n\u26a0\ufe0f Enable Interactivity in your Slack app settings and set the request URL to your n8n webhook-waiting URL, otherwise the buttons will open in a browser instead of responding inline in Slack."
},
"typeVersion": 1
},
{
"id": "ba0802b9-7d97-4efc-a1c7-10e1ff93c7f8",
"name": "Sticky Note \u2014 Section 5",
"type": "n8n-nodes-base.stickyNote",
"position": [
6656,
2544
],
"parameters": {
"color": 7,
"width": 360,
"height": 404,
"content": "## 5. Send survey to attendees\nOn approval, sends a branded email to all meeting attendees with the Weavely share link. Rejected surveys are silently discarded (false branch of the If node)."
},
"typeVersion": 1
},
{
"id": "set-config",
"name": "\u2699\ufe0f Configuration",
"type": "n8n-nodes-base.set",
"position": [
3760,
3460
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "config-calendar-id",
"name": "calendarId",
"type": "string",
"value": "user@example.com"
},
{
"id": "config-slack-user-id",
"name": "slackUserId",
"type": "string",
"value": "YOUR_SLACK_USER_ID"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a7dfa840-0fe6-487b-8d97-4fa6a56d157e",
"name": "Google Calendar Trigger",
"type": "n8n-nodes-base.googleCalendarTrigger",
"position": [
4192,
2720
],
"parameters": {
"options": {
"matchTerm": "Meeting"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "eventEnded",
"calendarId": {
"__rl": true,
"mode": "id",
"value": "={{ $('\u2699\ufe0f Configuration').item.json.calendarId }}"
}
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "87a790ec-ad48-4ca9-bbc8-525b9bca0ace",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
4400,
2720
],
"parameters": {
"unit": "minutes",
"amount": 10
},
"typeVersion": 1.1
},
{
"id": "f4b1a847-e483-4429-af15-88d572af6a6c",
"name": "Get an event",
"type": "n8n-nodes-base.googleCalendar",
"position": [
4576,
2720
],
"parameters": {
"eventId": "={{ $('Google Calendar Trigger').item.json.id }}",
"options": {},
"calendar": {
"__rl": true,
"mode": "id",
"value": "={{ $('\u2699\ufe0f Configuration').item.json.calendarId }}"
},
"operation": "get"
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "18b92f78-96b2-475f-9883-a474c08d7f53",
"name": "Filter Meeting Notes File",
"type": "n8n-nodes-base.code",
"position": [
4800,
2720
],
"parameters": {
"jsCode": "const attachments = $input.item.json.attachments || [];\n\nconst notesDoc = attachments.find(a => a.title === \"Notes by Gemini\");\n\nreturn [{\n json: {\n notesFileId: notesDoc?.fileId || null,\n notesFound: !!notesDoc,\n }\n}];"
},
"typeVersion": 2
},
{
"id": "5ce06644-b9d7-4994-9b48-4be84c6c3bc0",
"name": "Get a document",
"type": "n8n-nodes-base.googleDocs",
"position": [
5072,
2720
],
"parameters": {
"operation": "get",
"documentURL": "={{ $json.notesFileId }}"
},
"credentials": {
"googleDocsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "e1e299f1-41a9-43bc-9396-0710626e9c0f",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
5408,
2720
],
"parameters": {
"text": "=You are an expert at designing concise, effective post-meeting surveys.\n\nBased on the meeting notes provided, generate 5-7 survey questions that will help \nthe meeting organiser collect meaningful feedback from attendees.\n\nRules:\n- Questions must be directly based on the topics actually discussed in the meeting, \n not generic meeting feedback questions\n- Use a mix of: rating scales (1-5), yes/no, and max 2 open-ended questions\n- Keep every question short and unambiguous\n- Focus on: outcomes achieved, decisions made, action item clarity, and overall value\n\nUse the provided Weavely Forms tool to create the survey, once it's fully created publish it and only return the editor link provided by the tool.\n\n {{ $json.content }}",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 3
},
{
"id": "c13c006b-dfe8-4e95-983c-52e258609f0a",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
5344,
2912
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "be604586-e756-49ad-938f-fed4a8256405",
"name": "MCP Client",
"type": "@n8n/n8n-nodes-langchain.mcpClientTool",
"position": [
5504,
2912
],
"parameters": {
"options": {},
"endpointUrl": "https://mcp.weavely.ai/mcp"
},
"typeVersion": 1.2
},
{
"id": "b8a330fa-35ef-46dc-9722-9587121d5aef",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
5664,
2912
],
"parameters": {
"jsonSchemaExample": "{\n\t\"editor URL\": \"https://...\"\n}"
},
"typeVersion": 1.3
},
{
"id": "9cfd06ec-0cf6-40ed-ab47-0537104b7cdd",
"name": "Get Share URL",
"type": "n8n-nodes-base.code",
"position": [
5792,
2720
],
"parameters": {
"jsCode": "const output = $('AI Agent').item.json.output;\nconst url = typeof output === 'string' ? output : output['editor URL'];\nconst shadowFormId = url.split('/').pop();\n\n// Step 1: Fetch shadow form spec\nconst spec = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://api.weavely.ai/forms/${shadowFormId}/client`,\n headers: {\n 'Accept': 'application/json'\n }\n});\n\n// Step 2: Create anonymous copy owned by the user\nconst newForm = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.weavely.ai/v1/forms',\n headers: {\n 'Content-Type': 'application/json',\n 'Origin': 'https://forms.weavely.ai',\n 'Referer': 'https://forms.weavely.ai/'\n },\n body: JSON.stringify({\n name: spec.name || 'Post-Meeting Survey',\n publish: false,\n formJSON: spec.formJSON,\n themeJSON: spec.themeJSON,\n settings: spec.settings,\n logicRules: spec.logicRules || [],\n eventTriggers: spec.eventTriggers || []\n })\n});\n\nreturn [{\n json: {\n shareUrl: 'https://forms.weavely.ai/' + newForm.id,\n claimUrl: newForm.editor + '?publish=true',\n formId: newForm.id\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ca62f96f-7c08-44bb-ae92-bf6c6a235f8a",
"name": "Send message and wait for response",
"type": "n8n-nodes-base.slack",
"position": [
6144,
2768
],
"parameters": {
"user": {
"__rl": true,
"mode": "id",
"value": "={{ $('\u2699\ufe0f Configuration').item.json.slackUserId }}"
},
"message": "=New post-meeting survey ready for your approval:\n\n*Meeting:* {{ $('Get an event').item.json.summary }}\n\n*Step 1:* Click to claim and publish your form in Weavely (free account required):\n{{ $('Get Share URL').item.json.claimUrl }}\n\n*Step 2:* Once published, click Approve below to send the survey to all attendees.",
"options": {},
"operation": "sendAndWait",
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "54c3d98c-4a56-4bf8-9669-993278d932c1",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
6400,
2768
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "935f9a3e-519e-41e0-8a37-c45f03716d80",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.data.approved }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.3
},
{
"id": "5f18f5bf-7b5e-488e-82c7-483d094352b7",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
6784,
2752
],
"parameters": {
"sendTo": "={{ $('Get an event').item.json.attendees.map(item => item[\"email\"]).join(', ') }}",
"message": "=<!DOCTYPE html>\n<html>\n<body style=\"margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f4f4;padding:40px 0;\">\n <tr>\n <td align=\"center\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"background:#000000;padding:32px 40px;\">\n <p style=\"margin:0;color:#ffffff;font-size:13px;letter-spacing:0.05em;text-transform:uppercase;\">Post-meeting survey</p>\n <h1 style=\"margin:8px 0 0;color:#ffffff;font-size:24px;font-weight:600;line-height:1.3;\">{{ $('Get an event').item.json.summary }}</h1>\n </td>\n </tr>\n <tr>\n <td style=\"padding:40px;\">\n <p style=\"margin:0 0 16px;color:#333333;font-size:15px;line-height:1.6;\">Hi,</p>\n <p style=\"margin:0 0 24px;color:#333333;font-size:15px;line-height:1.6;\">Thanks for joining today's meeting. We'd love your quick feedback \u2014 it takes less than 2 minutes and helps us make future meetings more valuable.</p>\n <table cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 32px;\">\n <tr>\n <td style=\"background:#000000;border-radius:6px;\">\n <a href=\"{{ $('Get Share URL').item.json.shareUrl }}\" target=\"_blank\" style=\"display:inline-block;padding:14px 28px;color:#ffffff;font-size:15px;font-weight:600;text-decoration:none;\">Take the survey \u2192</a>\n </td>\n </tr>\n </table>\n <p style=\"margin:0 0 8px;color:#888888;font-size:13px;line-height:1.5;\">Or copy this link into your browser:</p>\n <p style=\"margin:0 0 32px;font-size:13px;line-height:1.5;\"><a href=\"{{ $('Get Share URL').item.json.shareUrl }}\" style=\"color:#000000;word-break:break-all;\">{{ $('Get Share URL').item.json.shareUrl }}</a></p>\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 32px;\"><tr><td style=\"border-top:1px solid #eeeeee;\"></td></tr></table>\n <p style=\"margin:0;color:#888888;font-size:13px;line-height:1.6;\">This survey was automatically generated based on your meeting. If you have any questions, reply directly to this email.</p>\n </td>\n </tr>\n <tr>\n <td style=\"background:#f9f9f9;padding:24px 40px;border-top:1px solid #eeeeee;\">\n <p style=\"margin:0;color:#aaaaaa;font-size:12px;line-height:1.6;text-align:center;\">Powered by <a href=\"https://weavely.ai\" style=\"color:#aaaaaa;\">Weavely</a></p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>",
"options": {},
"subject": "={{ $('Get an event').item.json.summary }} \u2014 Post-meeting survey"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
}
],
"connections": {
"If": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "Get an event",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Get Share URL",
"type": "main",
"index": 0
}
]
]
},
"MCP Client": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get an event": {
"main": [
[
{
"node": "Filter Meeting Notes File",
"type": "main",
"index": 0
}
]
]
},
"Get Share URL": {
"main": [
[
{
"node": "Send message and wait for response",
"type": "main",
"index": 0
}
]
]
},
"Get a document": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Calendar Trigger": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Filter Meeting Notes File": {
"main": [
[
{
"node": "Get a document",
"type": "main",
"index": 0
}
]
]
},
"Send message and wait for response": {
"main": [
[
{
"node": "If",
"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.
gmailOAuth2googleCalendarOAuth2ApigoogleDocsOAuth2ApiopenAiApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Stop forgetting to send follow-up surveys. This workflow automatically generates a tailored post-meeting survey from your Google Meet notes the moment a meeting ends, and sends it to all attendees with one Slack approval.
Source: https://n8n.io/workflows/15095/ — 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.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
This workflow automatically converts unstructured internal documentation into clear, actionable Standard Operating Procedures (SOPs).
Agent Youtube (Synthese,Notif,Idee Vidéo). Uses gmail, lmChatOpenAi, agent, slack. Event-driven trigger; 13 nodes.
Workflow Youtube. Uses n8n-nodes-youtube-transcript, gmail, lmChatOpenAi, agent. Event-driven trigger; 12 nodes.
Agent Nodes. Uses lmChatOpenAi, slack, stopAndError, errorTrigger. Event-driven trigger; 72 nodes.