This workflow corresponds to n8n.io template #15093 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets 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": "5b875e1a-dc46-4d29-b26c-ec23dd40143d",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
112,
1472
],
"parameters": {
"color": 4,
"width": 492,
"height": 972,
"content": "## Weekly Meeting Hours Report \u2014 Fireflies + Google Sheets + Gmail\n\nFor team leads, operations managers, and founders who want an automatic weekly summary of all meeting activity from Fireflies. Every Friday at 5PM the workflow pulls the last 7 days of transcripts, calculates seven meeting metrics, logs one summary row to Google Sheets for trend tracking, and emails a formatted report to your manager inbox.\n\n## How it works\n- **1. Schedule \u2014 Every Friday 5PM** triggers the workflow automatically each week\n- **2. Set \u2014 Config Values** stores your Fireflies API key, Google Sheet ID, recipient email, sender name, and company name\n- **3. HTTP \u2014 Fetch Transcripts** pulls the 100 most recent Fireflies transcripts for filtering\n- **4. Code \u2014 Calculate Meeting Metrics** filters to the past 7 days and calculates total meetings, total hours, average duration, busiest day, top participant, longest meeting, a day-by-day breakdown, and a numbered meeting list\n- **5. IF \u2014 Meetings Found?** stops cleanly if no meetings were recorded this week\n- **6. Google Sheets \u2014 Log Weekly Summary** appends one summary row per week to the tracker sheet\n- **7. Gmail \u2014 Send Weekly Report** sends a formatted plain-text email with all metrics, daily breakdown, and full meeting list\n\n## Set up steps\n1. In **2. Set \u2014 Config Values** \u2014 replace YOUR_FIREFLIES_API_KEY, YOUR_GOOGLE_SHEET_ID, sheet tab name, manager email address, sender name, and company name\n2. In **6. Google Sheets \u2014 Log Weekly Summary** \u2014 connect your Google Sheets OAuth2 credential\n3. In **7. Gmail \u2014 Send Weekly Report** \u2014 connect your Gmail OAuth2 credential using the account you want to send from\n4. Create a Google Sheet tab named Weekly Meeting Report with columns: Week, Total Meetings, Total Hours, Avg Duration (min), Busiest Day, Top Participant, Longest Meeting, All Participants, Logged At\n5. Activate the workflow \u2014 it runs every Friday at 5PM automatically"
},
"typeVersion": 1
},
{
"id": "0b4e08dd-915e-4580-bb6b-658ceda14433",
"name": "Section \u2014 Schedule, Config, and Transcript Fetch",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
1648
],
"parameters": {
"color": 5,
"width": 644,
"height": 420,
"content": "## Schedule, Config, and Transcript Fetch\nWorkflow triggers every Friday at 5PM. Config stores all credentials and report settings. HTTP fetches the 100 most recent Fireflies transcripts for the 7-day filter."
},
"typeVersion": 1
},
{
"id": "982f62e0-e439-4f85-aa0b-919fe1d63ae3",
"name": "Section \u2014 Metrics Calculation and Gate",
"type": "n8n-nodes-base.stickyNote",
"position": [
1408,
1600
],
"parameters": {
"color": 6,
"width": 436,
"height": 580,
"content": "## Metrics Calculation and Gate\nFilters transcripts to the past 7 days. Calculates total meetings, hours, average duration, busiest day, top participant, longest meeting, daily breakdown, and full meeting list. If no meetings found this week, exits cleanly via the Set node below."
},
"typeVersion": 1
},
{
"id": "19524926-436c-484b-9a84-8686cc408851",
"name": "Section \u2014 Weekly Log and Email Report",
"type": "n8n-nodes-base.stickyNote",
"position": [
2032,
1488
],
"parameters": {
"color": 4,
"width": 500,
"height": 900,
"content": "## Weekly Log and Email Report\nGoogle Sheets logs one summary row per week for trend tracking. Gmail sends a formatted plain-text report with all metrics, day-by-day breakdown, and numbered meeting list to the manager email."
},
"typeVersion": 1
},
{
"id": "176668fb-845d-41ef-ae5c-dc1ed0afe72e",
"name": "1. Schedule \u2014 Every Friday 5PM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
784,
1840
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 17 * * 5"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "c14827d7-ec33-4434-8c9d-021b39534d0f",
"name": "2. Set \u2014 Config Values",
"type": "n8n-nodes-base.set",
"position": [
1008,
1840
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-001",
"name": "firefliesApiKey",
"type": "string",
"value": "YOUR_FIREFLIES_API_KEY"
},
{
"id": "cfg-002",
"name": "sheetId",
"type": "string",
"value": "YOUR_GOOGLE_SHEET_ID"
},
{
"id": "cfg-003",
"name": "sheetName",
"type": "string",
"value": "Weekly Meeting Report"
},
{
"id": "cfg-004",
"name": "managerEmail",
"type": "string",
"value": "user@example.com"
},
{
"id": "cfg-005",
"name": "senderName",
"type": "string",
"value": "YOUR NAME"
},
{
"id": "cfg-006",
"name": "companyName",
"type": "string",
"value": "YOUR COMPANY NAME"
},
{
"id": "cfg-007",
"name": "weekStart",
"type": "string",
"value": "={{ $now.minus({days: 7}).toFormat('dd MMM yyyy') }}"
},
{
"id": "cfg-008",
"name": "weekEnd",
"type": "string",
"value": "={{ $now.toFormat('dd MMM yyyy') }}"
},
{
"id": "cfg-009",
"name": "sevenDaysAgoMs",
"type": "number",
"value": "={{ $now.minus({days: 7}).toMillis() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "900ab8fc-bffd-40db-b209-64f07458fe1e",
"name": "3. HTTP \u2014 Fetch Transcripts",
"type": "n8n-nodes-base.httpRequest",
"position": [
1232,
1840
],
"parameters": {
"url": "https://api.fireflies.ai/graphql",
"method": "POST",
"options": {},
"jsonBody": "={\n \"query\": \"query GetTranscripts { transcripts(limit: 100) { id title date duration participants } }\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "=Bearer {{ $json.firefliesApiKey }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "60808b1f-2321-430f-a8a1-e7e7723a5333",
"name": "4. Code \u2014 Calculate Meeting Metrics",
"type": "n8n-nodes-base.code",
"position": [
1456,
1840
],
"parameters": {
"jsCode": "const response = $input.first().json;\nconst config = $('2. Set \u2014 Config Values').item.json;\n\nconst all = response?.data?.transcripts || [];\nconst cutoff = config.sevenDaysAgoMs;\nconst meetings = all.filter(t => (t.date || 0) >= cutoff);\n\nif (meetings.length === 0) {\n return [{\n json: {\n hasMeetings: false,\n weekStart: config.weekStart,\n weekEnd: config.weekEnd,\n managerEmail: config.managerEmail,\n senderName: config.senderName,\n companyName: config.companyName,\n sheetId: config.sheetId,\n sheetName: config.sheetName\n }\n }];\n}\n\nconst totalMeetings = meetings.length;\nconst totalSeconds = meetings.reduce((sum, m) => sum + (m.duration || 0), 0);\nconst totalMinutes = Math.round(totalSeconds / 60);\nconst totalHours = (totalMinutes / 60).toFixed(1);\nconst avgDuration = Math.round(totalMinutes / totalMeetings);\n\nconst dayCount = {};\nconst dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];\nmeetings.forEach(m => {\n const day = dayNames[new Date(m.date).getDay()];\n dayCount[day] = (dayCount[day] || 0) + 1;\n});\nconst busiestDay = Object.entries(dayCount).sort((a,b) => b[1]-a[1])[0];\nconst busiestDayLabel = busiestDay ? busiestDay[0] + ' (' + busiestDay[1] + ' meetings)' : 'N/A';\n\nconst participantCount = {};\nmeetings.forEach(m => {\n (m.participants || []).forEach(p => {\n if (p && p.trim()) participantCount[p.trim()] = (participantCount[p.trim()] || 0) + 1;\n });\n});\nconst topParticipant = Object.entries(participantCount).sort((a,b) => b[1]-a[1])[0];\nconst topParticipantLabel = topParticipant ? topParticipant[0] + ' (' + topParticipant[1] + ' meetings)' : 'N/A';\n\nconst longest = meetings.reduce((max, m) => (m.duration||0) > (max.duration||0) ? m : max, meetings[0]);\nconst longestLabel = (longest.title || 'Untitled') + ' \u2014 ' + Math.round((longest.duration||0)/60) + ' min';\n\nconst allParticipants = [...new Set(\n meetings.flatMap(m => m.participants || []).filter(p => p && p.trim())\n)].join(', ');\n\nconst dailyBreakdown = {};\nmeetings.forEach(m => {\n const date = new Date(m.date).toLocaleDateString('en-GB', { weekday:'short', day:'2-digit', month:'short' });\n if (!dailyBreakdown[date]) dailyBreakdown[date] = { count: 0, minutes: 0, ts: m.date };\n dailyBreakdown[date].count++;\n dailyBreakdown[date].minutes += Math.round((m.duration||0)/60);\n});\nlet dailyText = '';\nObject.entries(dailyBreakdown)\n .sort((a,b) => a[1].ts - b[1].ts)\n .forEach(([day, data]) => {\n dailyText += day + ': ' + data.count + ' meeting(s), ' + data.minutes + ' min\\n';\n });\n\nlet meetingList = '';\nmeetings\n .sort((a,b) => (b.date||0) - (a.date||0))\n .forEach((m, i) => {\n const date = new Date(m.date).toLocaleDateString('en-GB', { day:'2-digit', month:'short' });\n const dur = Math.round((m.duration||0)/60);\n meetingList += (i+1) + '. ' + date + ' \u2014 ' + (m.title||'Untitled') + ' (' + dur + ' min)\\n';\n });\n\nconst loggedAt = new Date().toISOString().replace('T',' ').substring(0,16);\n\nreturn [{\n json: {\n hasMeetings: true,\n weekLabel: config.weekStart + ' to ' + config.weekEnd,\n totalMeetings,\n totalHours,\n avgDuration,\n busiestDay: busiestDayLabel,\n topParticipant: topParticipantLabel,\n longestMeeting: longestLabel,\n allParticipants,\n loggedAt,\n dailyText,\n meetingList,\n totalMinutes,\n weekStart: config.weekStart,\n weekEnd: config.weekEnd,\n managerEmail: config.managerEmail,\n senderName: config.senderName,\n companyName: config.companyName,\n sheetId: config.sheetId,\n sheetName: config.sheetName\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7ef1b60b-db65-4338-8954-06cbd44b088b",
"name": "5. IF \u2014 Meetings Found?",
"type": "n8n-nodes-base.if",
"position": [
1664,
1840
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-001",
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.hasMeetings }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "24c8e6c1-bf85-47a2-b24f-1fdf905b22dd",
"name": "8. Set \u2014 No Meetings Skip",
"type": "n8n-nodes-base.set",
"position": [
2240,
2160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "no-mtg-001",
"name": "result",
"type": "string",
"value": "=No meetings found in Fireflies for {{ $json.weekStart }} to {{ $json.weekEnd }}. No report sent."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "146517d9-86fb-4cc1-9351-5c99da29d707",
"name": "6. Google Sheets \u2014 Log Weekly Summary",
"type": "n8n-nodes-base.googleSheets",
"position": [
2112,
1824
],
"parameters": {
"columns": {
"value": {
"Week": "={{ $json.weekLabel }}",
"Logged At": "={{ $json.loggedAt }}",
"Busiest Day": "={{ $json.busiestDay }}",
"Total Hours": "={{ $json.totalHours }}",
"Total Meetings": "={{ $json.totalMeetings }}",
"Longest Meeting": "={{ $json.longestMeeting }}",
"Top Participant": "={{ $json.topParticipant }}",
"All Participants": "={{ $json.allParticipants }}",
"Avg Duration (min)": "={{ $json.avgDuration }}"
},
"mappingMode": "defineBelow"
},
"options": {
"cellFormat": "USER_ENTERED"
},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $json.sheetName }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.sheetId }}"
}
},
"typeVersion": 4.5
},
{
"id": "211923e9-c335-4377-a67e-20a52c88545b",
"name": "7. Gmail \u2014 Send Weekly Report",
"type": "n8n-nodes-base.gmail",
"position": [
2336,
1824
],
"parameters": {
"sendTo": "={{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.managerEmail }}",
"message": "=Hi,\n\nHere is your weekly meeting hours report for {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.companyName }}.\n\nWEEK: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.weekStart }} to {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.weekEnd }}\n\nSUMMARY\nTotal Meetings: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.totalMeetings }}\nTotal Time in Meetings: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.totalHours }} hours ({{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.totalMinutes }} minutes)\nAverage Meeting Duration: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.avgDuration }} minutes\nBusiest Day: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.busiestDay }}\nTop Participant: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.topParticipant }}\nLongest Meeting: {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.longestMeeting }}\n\nDAY BY DAY BREAKDOWN\n{{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.dailyText }}\nALL MEETINGS THIS WEEK\n{{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.meetingList }}\nFull data has been logged to Google Sheets for trend tracking.\n\nBest regards,\n{{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.senderName }}\n{{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.companyName }}\n\n---\nAuto-generated by n8n + Fireflies.ai every Friday",
"options": {
"appendAttribution": false
},
"subject": "=Weekly Meeting Report \u2014 {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.companyName }} \u2014 {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.weekStart }} to {{ $('4. Code \u2014 Calculate Meeting Metrics').item.json.weekEnd }}"
},
"typeVersion": 2.1
}
],
"connections": {
"2. Set \u2014 Config Values": {
"main": [
[
{
"node": "3. HTTP \u2014 Fetch Transcripts",
"type": "main",
"index": 0
}
]
]
},
"5. IF \u2014 Meetings Found?": {
"main": [
[
{
"node": "6. Google Sheets \u2014 Log Weekly Summary",
"type": "main",
"index": 0
}
],
[
{
"node": "8. Set \u2014 No Meetings Skip",
"type": "main",
"index": 0
}
]
]
},
"3. HTTP \u2014 Fetch Transcripts": {
"main": [
[
{
"node": "4. Code \u2014 Calculate Meeting Metrics",
"type": "main",
"index": 0
}
]
]
},
"1. Schedule \u2014 Every Friday 5PM": {
"main": [
[
{
"node": "2. Set \u2014 Config Values",
"type": "main",
"index": 0
}
]
]
},
"4. Code \u2014 Calculate Meeting Metrics": {
"main": [
[
{
"node": "5. IF \u2014 Meetings Found?",
"type": "main",
"index": 0
}
]
]
},
"6. Google Sheets \u2014 Log Weekly Summary": {
"main": [
[
{
"node": "7. Gmail \u2014 Send Weekly Report",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Activate this workflow once and every Friday at 5PM it automatically pulls your week's meeting data from Fireflies, calculates seven metrics, and emails a formatted report to your manager inbox. It tracks total meetings, total hours, average duration, busiest day, top…
Source: https://n8n.io/workflows/15093/ — 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.
Automatically extract structured information from emails using AI-powered document analysis. This workflow processes emails from specified domains, classifies them by type, and extracts structured dat
What This Flow Does
This n8n template allows you to automatically monitor your company's budget by comparing live Bexio accounting data against targets defined in Google Sheets, sending automated weekly email reports. It
This workflow streamlines HR outreach by fetching contact data, validating emails, enforcing daily sending limits, and sending personalized emails with attachments, all while logging activity. Read HR
This workflow automatically monitors solar energy production every 2 hours by fetching data from the Energidataservice API. If the energy output falls below a predefined threshold, it instantly notifi