AutomationFlowsWeb Scraping › Lunajoy - Post-meeting Auto Analysis (daily)

Lunajoy - Post-meeting Auto Analysis (daily)

LunaJoy - Post-Meeting Auto Analysis (Daily). Uses googleCalendar, googleDrive, httpRequest. Scheduled trigger; 9 nodes.

Cron / scheduled trigger★★★★☆ complexity9 nodesGoogle CalendarGoogle DriveHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 9 Complexity: ★★★★☆ Added:

This workflow follows the Google Calendar → 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
{
  "name": "LunaJoy - Post-Meeting Auto Analysis (Daily)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 23,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000001",
      "name": "Every Night 23:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "REPLACE_WITH_CALENDAR_ID",
          "mode": "list",
          "cachedResultName": "LunaJoy Shared Calendar"
        },
        "returnAll": true,
        "options": {
          "timeMin": "={{ new Date(Date.now() - 36*60*60*1000).toISOString() }}",
          "timeMax": "={{ new Date().toISOString() }}",
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000002",
      "name": "Get Yesterday's Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        440,
        300
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-1",
              "leftValue": "={{ $json.hangoutLink }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "cond-2",
              "leftValue": "={{ ($json.attachments || []).length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            },
            {
              "id": "cond-3",
              "leftValue": "={{ $json.status }}",
              "rightValue": "cancelled",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000003",
      "name": "Has Meet + Attachments",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "// Extract transcript + recording from the Calendar event's attachments.\n//\n// Google Meet attaches a single Google Doc titled \"Notes by Gemini\" that\n// contains BOTH the AI summary AND the verbatim transcript inline. We also\n// accept Docs titled \"Transcript\" as a fallback for older meetings.\nconst event = $json;\nconst attachments = event.attachments || [];\n\nconst isGoogleDoc = (a) => a && a.mimeType === 'application/vnd.google-apps.document';\n\n// Prefer explicit Transcript doc; fall back to Notes by Gemini; finally any Doc.\nconst transcriptAtt =\n  attachments.find(a => isGoogleDoc(a) && /transcript/i.test(a.title || '')) ||\n  attachments.find(a => isGoogleDoc(a) && /notes by gemini|gemini notes/i.test(a.title || '')) ||\n  attachments.find(a => isGoogleDoc(a));\n\n// Recording = video file OR attachment whose title contains 'Recording'\nconst recordingAtt = attachments.find(a =>\n  (a.mimeType && a.mimeType.startsWith('video/')) ||\n  /recording/i.test(a.title || '')\n);\n\n// If no Google Doc attached at all, mark this item to be skipped downstream\nif (!transcriptAtt) {\n  return {\n    skip: true,\n    calendarEventId: event.id,\n    meetingTitle: event.summary || 'Untitled Meeting',\n    reason: 'no transcript/notes doc attached',\n    attachmentTitles: attachments.map(a => a.title || '(untitled)')\n  };\n}\n\nreturn {\n  skip: false,\n  calendarEventId: event.id,\n  meetingTitle: event.summary || 'Untitled Meeting',\n  meetingDate: event.start?.dateTime || event.start?.date || null,\n  attendees: (event.attendees || [])\n    .filter(a => a && a.email && !a.self)\n    .map(a => a.email),\n  transcriptFileId: transcriptAtt.fileId,\n  transcriptDocUrl: transcriptAtt.fileUrl,\n  transcriptSourceTitle: transcriptAtt.title || null,\n  recordingUrl: recordingAtt ? recordingAtt.fileUrl : null\n};\n"
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000004",
      "name": "Extract Attachments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "skip-filter",
              "leftValue": "={{ $json.skip }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000005",
      "name": "Has Transcript?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1160,
        300
      ]
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.transcriptFileId }}",
          "mode": "id"
        },
        "options": {
          "googleFileConversion": {
            "conversion": {
              "docsToFormat": "text/plain"
            }
          }
        }
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000006",
      "name": "Download Transcript Doc",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1400,
        300
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "text",
        "binaryPropertyName": "data",
        "destinationKey": "transcriptText",
        "options": {}
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000007",
      "name": "Binary to Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        1640,
        300
      ]
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "meetingTitle",
              "value": "={{ $('Extract Attachments').item.json.meetingTitle }}",
              "type": "string"
            },
            {
              "id": "a2",
              "name": "meetingDate",
              "value": "={{ $('Extract Attachments').item.json.meetingDate }}",
              "type": "string"
            },
            {
              "id": "a3",
              "name": "calendarEventId",
              "value": "={{ $('Extract Attachments').item.json.calendarEventId }}",
              "type": "string"
            },
            {
              "id": "a4",
              "name": "attendees",
              "value": "={{ $('Extract Attachments').item.json.attendees }}",
              "type": "array"
            },
            {
              "id": "a5",
              "name": "transcript",
              "value": "={{ $json.transcriptText }}",
              "type": "string"
            },
            {
              "id": "a6",
              "name": "recordingUrl",
              "value": "={{ $('Extract Attachments').item.json.recordingUrl }}",
              "type": "string"
            },
            {
              "id": "a7",
              "name": "transcriptDocUrl",
              "value": "={{ $('Extract Attachments').item.json.transcriptDocUrl }}",
              "type": "string"
            },
            {
              "id": "a8",
              "name": "userId",
              "value": "REPLACE_WITH_SUPABASE_USER_UUID",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": false,
        "options": {}
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000008",
      "name": "Build Webhook Body",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1880,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://lunajoy.vercel.app/api/webhooks/n8n/post-meeting",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 2000
            }
          }
        }
      },
      "id": "d3f1a0e0-0000-4000-8000-000000000009",
      "name": "POST to Webhook",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2120,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Every Night 23:00": {
      "main": [
        [
          {
            "node": "Get Yesterday's Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Yesterday's Events": {
      "main": [
        [
          {
            "node": "Has Meet + Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Meet + Attachments": {
      "main": [
        [
          {
            "node": "Extract Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Attachments": {
      "main": [
        [
          {
            "node": "Has Transcript?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Transcript?": {
      "main": [
        [
          {
            "node": "Download Transcript Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Transcript Doc": {
      "main": [
        [
          {
            "node": "Binary to Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Binary to Text": {
      "main": [
        [
          {
            "node": "Build Webhook Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Webhook Body": {
      "main": [
        [
          {
            "node": "POST to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}

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

LunaJoy - Post-Meeting Auto Analysis (Daily). Uses googleCalendar, googleDrive, httpRequest. Scheduled trigger; 9 nodes.

Source: https://github.com/beatriz1508/lunajoy/blob/63d07139f1a14993f76c3fdb0742b01b7c16de90/n8n/post-meeting-daily.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

[TEMPLATE] Full Class -> Calendar Sync. Uses httpRequest, googleCalendar. Scheduled trigger; 24 nodes.

HTTP Request, Google Calendar
Web Scraping

This workflow creates a daily, automated backup of all workflows in a self-hosted n8n instance and stores them in Google Drive. Instead of exporting every workflow on every run, it uses content hashin

HTTP Request, Google Drive, Data Table +1
Web Scraping

Teams that track absences in Everhour and want a shared Google Calendar view for quick planning. Ideal for managers, HR/OPS, and teammates who need instant visibility into approved time off. Pulls app

HTTP Request, Google Calendar
Web Scraping

🕌 How it works

Google Calendar, HTTP Request
Web Scraping

This workflow automatically creates daily backups of all n8n workflows and stores them in Google Drive, using the n8n API to export workflows and a scheduled retention policy to keep storage organized

Google Drive, HTTP Request