{
  "name": "Calendar and Meeting Prep Workflow",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1088,
        112
      ],
      "id": "95c50779-76bf-4dc2-9b92-56581bf776d4",
      "name": "When clicking \u2018Execute workflow\u2019"
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "Your_calender",
          "mode": "list",
          "cachedResultName": "Your_calender"
        },
        "timeMax": "={{ $now.plus({ day: 1 }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        -864,
        112
      ],
      "id": "b37fe739-a09a-4fce-b43f-92c2b1a59332",
      "name": "Fetch all meetings for the next day",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const ORGANIZER_EMAIL = 'YOUR_ORGANIZER_EMAIL';\nconst INTERNAL_DOMAIN = 'YOUR_INTERNAL_DOMAIN.com';\nconst SKIP_KEYWORDS = ['focus', 'block', 'lunch', 'ooo', 'hold', 'reminder', 'break'];\n\nconst results = [];\n\nfor (const item of $input.all()) {\n  const event = item.json;\n\n  // skip all-day events\n  if (!event.start?.dateTime) continue;\n\n  // skip by title keywords\n  const title = (event.summary ?? '').toLowerCase();\n  if (SKIP_KEYWORDS.some(k => title.includes(k))) continue;\n\n  // remove organizer\n  const others = (event.attendees ?? []).filter(a => a.email !== ORGANIZER_EMAIL);\n\n  // skip solo blocks\n  if (others.length === 0) continue;\n\n  // classify by domain\n  const external = others.filter(a => !a.email.endsWith(INTERNAL_DOMAIN));\n  const internal = others.filter(a => a.email.endsWith(INTERNAL_DOMAIN));\n\n  // meeting type\n  let meetingType;\n  if (external.length === 0) {\n    meetingType = 'internal';\n  } else if (external.length === 1) {\n    meetingType = 'external_1to1';\n  } else {\n    meetingType = 'group';\n  }\n\n  results.push({\n    json: {\n      title: event.summary ?? 'Untitled Meeting',\n      startTime: event.start.dateTime,\n      endTime: event.end.dateTime,\n      isRecurring: !!event.recurringEventId,\n      meetingType,\n      noMeetings: false,\n      allAttendees: others,\n      externalAttendees: external,\n      internalAttendees: internal,\n      primaryAttendee: external[0] ?? null,\n    }\n  });\n}\n\nif (results.length === 0) {\n  return [{ json: { noMeetings: true } }];\n}\n\nreturn results;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -640,
        112
      ],
      "id": "2d255445-1007-41e9-a1a8-40c1b0d0b2f0",
      "name": "Filter real meeting"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "f745dd26-83bc-45d8-9f55-c203098d5089",
              "leftValue": "={{ $json.noMeetings }}",
              "rightValue": "true",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -416,
        112
      ],
      "id": "627ba699-83f7-4f09-9138-6b20b80628fc",
      "name": "Are there no meetings today?"
    },
    {
      "parameters": {
        "sendTo": "YOUR_EMAIL",
        "subject": "test",
        "message": "this is test",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        -192,
        0
      ],
      "id": "a62e7dfd-e84c-4b42-a1ed-deb9e579bcfc",
      "name": "Let founder know that there are no meetings",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.meetingType }}",
                    "rightValue": "internal",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "43ea319d-e6c5-42a7-bece-5e118a1e60d3"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "internal"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "300c100a-b45b-4ef2-addb-520ed6621578",
                    "leftValue": "={{ $json.meetingType }}",
                    "rightValue": "group",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Group"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "a3a14942-2757-4803-b939-6301b20f84d8",
                    "leftValue": "={{ $json.meetingType }}",
                    "rightValue": "external_1to1",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "external"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        -192,
        192
      ],
      "id": "04f58b29-472c-4247-8c16-bccf287f5e80",
      "name": "Switch"
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "getAll",
        "team": "YOUR_TEAM",
        "space": "YOUR_SPACE",
        "folder": "YOUR_FOLDER",
        "list": "YOUR_LIST",
        "filters": {}
      },
      "type": "n8n-nodes-base.clickUp",
      "typeVersion": 1,
      "position": [
        80,
        -112
      ],
      "id": "174fd3ef-8b47-4f83-8b05-692a24a09ee4",
      "name": "Get many tasks",
      "credentials": {
        "clickUpOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const meeting = $('Switch').item.json;\nconst internalAttendees = meeting.internalAttendees ?? [];\n\nconst fyxerData = {\n  'john@solsombra.com': {\n    lastMeetingDate: '2026-04-22',\n    summary: 'Discussed Q2 ops report timeline with John. CRM migration needs executive sign-off. BDM position description review outstanding.',\n    actionItems: [\n      'Submit Q2 ops report by Friday',\n      'Get executive sign-off on CRM migration',\n      'Review BDM position description'\n    ]\n  }\n};\n\nreturn [{\n  json: {\n    _type: 'fyxer',\n    meetingTitle: meeting.title,\n    meetingTime: meeting.startTime,\n    meetingType: 'internal',\n    isRecurring: meeting.isRecurring,\n    internalAttendees: internalAttendees,\n    summaries: internalAttendees.map(a => ({\n      attendeeEmail: a.email,\n      ...(fyxerData[a.email] ?? {\n        lastMeetingDate: null,\n        summary: null,\n        actionItems: []\n      })\n    }))\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        48
      ],
      "id": "abaae369-6d14-4f20-bd3e-700bd55190fc",
      "name": "Simulate Fyxer API to get meeting summary"
    },
    {
      "parameters": {
        "jsCode": "const allInputs = $input.all();\n\nconst fyxerInput = allInputs.find(i => i.json._type === 'fyxer');\nconst clickupTasks = allInputs.filter(i => i.json._type !== 'fyxer');\n\nconst meeting = fyxerInput.json;\nconst internalAttendees = meeting.internalAttendees ?? [];\nconst fyxerSummaries = meeting.summaries ?? [];\n\nconst result = internalAttendees.map(attendee => {\n  const email = attendee.email.toLowerCase();\n\n  const tasks = clickupTasks\n    .filter(task =>\n      (task.json.assignees ?? []).some(a =>\n        (a.email ?? '').toLowerCase() === email\n      )\n    )\n    .map(t => t.json.name ?? 'Unnamed task');\n\n  const fyxer = fyxerSummaries.find(f =>\n    (f.attendeeEmail ?? '').toLowerCase() === email\n  );\n\n  const hasSummary = fyxer?.summary != null;\n  const hasTasks = tasks.length > 0;\n\n  return {\n    email: attendee.email,\n    name: attendee.email.split('@')[0],\n    openTasks: hasTasks ? tasks : [],\n    noTasksFound: !hasTasks,\n    lastMeeting: hasSummary ? {\n      date: fyxer.lastMeetingDate,\n      summary: fyxer.summary,\n      actionItems: fyxer.actionItems ?? []\n    } : {\n      summary: 'No previous meeting summary found',\n      actionItems: []\n    }\n  };\n});\n\nreturn [{\n  json: {\n    meetingTitle: meeting.meetingTitle,\n    meetingTime: meeting.meetingTime,\n    meetingType: 'internal',\n    isRecurring: meeting.isRecurring,\n    attendees: result\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        656,
        -96
      ],
      "id": "ba56c82a-eaee-4886-8b9b-cb8664f29c3e",
      "name": "Match internal team members to their task and meeting summaries"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        416,
        -96
      ],
      "id": "5f8bb4c0-79b8-4c02-98c5-e07974606d25",
      "name": "Merge"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\nconst safe = (val) => val ?? '';\n\nconst formatMeeting = (meeting, type) => {\n  const attendees = Array.isArray(meeting.attendees) ? meeting.attendees : [];\n  \n  const attendeeLines = attendees.map(a => {\n    if (!a) return '';\n    const lines = [];\n\n    if (a.name) lines.push('Name: ' + a.name);\n    if (a.jobTitle && a.company) lines.push('Role: ' + a.jobTitle + ' at ' + a.company);\n    if (a.lifecycleStage) lines.push('CRM stage: ' + a.lifecycleStage);\n\n    if (a.deal && a.deal.dealName) {\n      let dealLine = 'Deal: ' + a.deal.dealName + ' \u2014 ' + safe(a.deal.dealStage);\n      if (a.deal.amount) dealLine += ' \u2014 ' + a.deal.amount;\n      lines.push(dealLine);\n    }\n\n    if (a.lastMeeting && a.lastMeeting.date) {\n      lines.push('Last meeting (' + a.lastMeeting.date + '): ' + safe(a.lastMeeting.summary));\n      if (Array.isArray(a.lastMeeting.actionItems) && a.lastMeeting.actionItems.length) {\n        lines.push('Outstanding actions: ' + a.lastMeeting.actionItems.join(' | '));\n      }\n    }\n\n    if (Array.isArray(a.emailThreads) && a.emailThreads.length) {\n      const emails = a.emailThreads.map(e => safe(e.subject) + ' (' + safe(e.date) + '): ' + safe(e.snippet));\n      lines.push('Recent emails: ' + emails.join(' | '));\n    }\n\n    if (Array.isArray(a.openTasks) && a.openTasks.length) {\n      const tasks = a.openTasks.map(t => {\n        let taskLine = safe(t.taskName) + ' \u2014 ' + safe(t.status);\n        if (t.dueDate) taskLine += ' \u2014 due ' + t.dueDate;\n        return taskLine;\n      });\n      lines.push('Open tasks: ' + tasks.join(' | '));\n    }\n\n    if (a.linkedin) {\n      if (a.linkedin.headline) lines.push('LinkedIn: ' + a.linkedin.headline);\n      if (Array.isArray(a.linkedin.recentPosts) && a.linkedin.recentPosts.length) {\n        lines.push('Recent posts: ' + a.linkedin.recentPosts.join(' | '));\n      }\n    }\n\n    return lines.join('\\n');\n  }).filter(Boolean).join('\\n\\n');\n\n  const header = '[' + type.toUpperCase() + ' \u2014 ' + safe(meeting.meetingTitle) + ' at ' + safe(meeting.meetingTime) + (meeting.isRecurring ? ' \u2014 RECURRING' : '') + ']';\n  return header + '\\n' + attendeeLines;\n};\n\nconst sections = [];\n\n(Array.isArray(data.internalMeetings) ? data.internalMeetings : []).forEach(m => {\n  sections.push(formatMeeting(m, 'internal'));\n});\n\n(Array.isArray(data.externalMeetings) ? data.externalMeetings : []).forEach(m => {\n  const type = m.isGroup ? 'group' : 'external 1:1';\n  sections.push(formatMeeting(m, type));\n});\n\nconst prompt = 'Date: ' + safe(data.date) + '\\nTotal meetings: ' + safe(data.totalMeetings) + '\\n\\n' + sections.join('\\n\\n---\\n\\n');\n\nconst body = {\n  model: 'claude-sonnet-4-20250514',\n  max_tokens: 4000,\n  system: `You are Landon's Chief of Staff at SolSombra, an operations and AI automation advisory firm. You prepare his daily meeting prep brief \u2014 a practical document he reads in 2 to 3 minutes before his day starts.\n\nWrite clearly and directly. No asterisks, no em dashes, no unnecessary punctuation, no markdown formatting symbols. Use plain text with natural sentence structure. Write like a sharp, experienced executive assistant who knows the business well.\n\nFor recurring meetings focus on what has changed since last time and what needs resolving today. Do not recap general background.\n\nIf a field is missing or null, skip it cleanly. Do not mention that data is unavailable. Just omit it.\n\nKeep each meeting section concise. Landon needs context not a report.\n\nReturn your response as a JSON object in this exact structure. No explanation, no markdown, no code fences, just raw JSON:\n\n{\n  \"internal\": [\n    {\n      \"time\": \"10:30 AM\",\n      \"title\": \"Weekly Team Sync\",\n      \"points\": [\n        \"bubu and John attending.\",\n        \"John has three outstanding actions from April 22: Q2 ops report due Friday, CRM migration sign-off pending, BDM position description still in review.\",\n        \"Focus on realistic completion dates and whether he needs support to close these items.\"\n      ]\n    }\n  ],\n  \"external\": [\n    {\n      \"time\": \"9:00 AM\",\n      \"title\": \"Discovery Call - Ibrahim Lawal\",\n      \"points\": [\n        \"Ibrahim Lawal, CTO at Microsoft.\",\n        \"Deal at proposal sent stage, worth $2500. Strong April 29 call confirmed digital transformation scope across three business units.\",\n        \"Revised proposal addressing legacy concerns is due today. CFO budget approval expected by May 7.\",\n        \"Push for CFO feedback timeline and get clearer picture on internal resistance from the legacy systems team.\"\n      ]\n    }\n  ],\n  \"group\": []\n}\n\nRules for the points array:\n- Each point is a single complete sentence or two short ones max\n- First point is always who is attending and their role\n- Then deal or relationship status if relevant\n- Then what happened last time and what is outstanding\n- Then any email or LinkedIn context\n- Then open or overdue tasks\n- Last point is always the suggested talking point or flag for Landon\n- If a section has no meetings return an empty array for it`,\n\n  messages: [{ role: 'user', content: prompt }]\n};\n\nreturn [{ json: body }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1952,
        -16
      ],
      "id": "9db6b6aa-6752-465f-b2f3-b11cdf990afb",
      "name": "Standardize prompt for Claude"
    },
    {
      "parameters": {
        "jsCode": "const allItems = $input.all();\nconst input = allItems[0].json;\nconst externalAttendees = input.externalAttendees ?? [];\n\nreturn externalAttendees.map(attendee => ({\n  json: {\n    meetingTitle: input.title,\n    meetingTime: input.startTime,\n    meetingType: input.meetingType,\n    isRecurring: input.isRecurring ?? false,\n    isGroup: true,\n    allExternalAttendees: externalAttendees,\n    primaryAttendee: attendee,\n    attendeeEmail: attendee.email,\n    attendeeName: attendee.displayName ?? attendee.email.split('@')[0]\n  }\n}));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        208
      ],
      "id": "023a3adf-c92f-4e65-b457-59ab534975a9",
      "name": "Tag as group + split to one item per external attendee"
    },
    {
      "parameters": {
        "jsCode": "const fyxerData = {\n  'ibrahim@microsoft.co': {\n    lastMeetingDate: '2026-04-29',\n    summary: 'Strong direction from discovery call with Ibrahim. Digital transformation scope confirmed across three business units. Budget approval process in flight \u2014 CFO sign-off expected within two weeks. Ibrahim flagged internal resistance from legacy systems team as key risk. Phased proposal approach agreed.',\n    actionItems: [\n      'Send phased proposal addressing legacy systems concern',\n      'Follow up on CFO budget approval by May 7',\n      'Share SolSombra case studies in similar enterprise context',\n      'Introduce Ibrahim to embedded-network contacts once proposal accepted'\n    ],\n    source: 'fyxer_real_data_anonymised'\n  },\n  'fatima@credpal.africa': {\n    lastMeetingDate: '2026-04-20',\n    summary: 'Check-in with Fatima on CredPal engagement. AI-driven optimisation for credit scoring infrastructure discussed. Fatima flagged risk of internal team replicating external consulting work \u2014 needs to be addressed in contract scope. Board sign-off on contract expected this week. Fatima personally keen to proceed.',\n    actionItems: [\n      'Chase board sign-off timeline \u2014 expected by May 7',\n      'Tighten contract scope to protect against internal replication risk',\n      'Confirm onboarding start date once signed',\n      'Prepare discovery session agenda for follow-up call'\n    ],\n    source: 'fyxer_real_data_anonymised'\n  },\n  'lara.okeke@medicorehealth.com': {\n    lastMeetingDate: '2026-04-15',\n    summary: 'Proposal review session with Lara and Grace \u2014 MediCore operations overhaul across 3 departments. Lara supportive of proceeding. Grace raised timeline concerns for phase 1. Data migration component flagged as needing clearer scoping before sign-off. Strong intent from both sides, timeline is the blocker.',\n    actionItems: [\n      'Address Grace timeline concerns \u2014 send compressed phase 1 option',\n      'Clarify data migration scope in revised proposal',\n      'Confirm which 3 departments are in scope for phase 1',\n      'Send revised proposal and timeline by end of this week'\n    ],\n    source: 'fyxer_real_data_anonymised'\n  },\n  'grace.eze@healthlink.com': {\n    lastMeetingDate: '2026-04-15',\n    summary: 'Same session as Lara \u2014 Grace represents the HealthLink partnership angle within the MediCore engagement. Primary concern is data interoperability and sharing terms. Grace wants HealthLink scoped as a partner not a subcontractor. Needs separate follow-up to clarify HealthLink specific terms before joint sign-off.',\n    actionItems: [\n      'Clarify data sharing and interoperability terms in proposal',\n      'Confirm HealthLink partnership structure \u2014 partner not subcontractor',\n      'Follow up with Grace separately from Lara on HealthLink specifics',\n      'Address timeline concern raised jointly with Lara'\n    ],\n    source: 'fyxer_real_data_anonymised'\n  }\n};\n\nreturn $input.all().map(item => {\n  const input = item.json;\n  const email = input.primaryAttendee?.email ?? input.attendeeEmail ?? '';\n  const isGroup = input.isGroup ?? false;\n\n  const data = fyxerData[email] ?? {\n    lastMeetingDate: null,\n    summary: null,\n    actionItems: [],\n    source: 'mock_fyxer'\n  };\n\n  return {\n    json: {\n      _type: 'fyxer',\n      attendeeEmail: email,\n      isGroup,\n      meetingTitle: input.meetingTitle,\n      meetingTime: input.meetingTime,\n      meetingType: input.meetingType,\n      isRecurring: input.isRecurring,\n      allExternalAttendees: input.allExternalAttendees ?? [],\n      primaryAttendee: input.primaryAttendee,\n      lastMeetingDate: data.lastMeetingDate,\n      summary: data.summary ?? 'No previous meeting found',\n      actionItems: data.actionItems,\n      source: data.source\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        784,
        384
      ],
      "id": "ece7aa88-77ec-45b3-9934-33ffe33e8640",
      "name": "Fyxer meeting summary simulation"
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map(item => {\n  const input = item.json;\n  \n  return {\n    json: {\n      meetingTitle: input.title,\n      meetingTime: input.startTime,\n      meetingType: input.meetingType,\n      isRecurring: input.isRecurring ?? false,\n      isGroup: false,\n      allExternalAttendees: input.externalAttendees ?? [],\n      attendees: input.externalAttendees ?? [],\n      primaryAttendee: input.primaryAttendee ?? null,\n      attendeeEmail: input.primaryAttendee?.email ?? null,\n      attendeeName: input.primaryAttendee?.email?.split('@')[0] ?? null\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        384
      ],
      "id": "b2f979b6-faf8-477f-ac3d-31612855dfed",
      "name": "Standardize external data route"
    },
    {
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/contacts?limit=100&properties=firstname,lastname,email,jobtitle,company,phone,lifecyclestage,hs_lead_status,notes_last_contacted,hs_sales_email_last_replied,hs_email_last_send_date,hs_email_last_replied,notes_last_updated,num_notes,hs_last_sales_activity_timestamp",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "authorization",
              "value": "Bearer YOUR_TOKEN"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        784,
        560
      ],
      "id": "43c90a73-d7e5-4300-bcb2-69728fce3242",
      "name": "Get Hubspot Contact record"
    },
    {
      "parameters": {
        "jsCode": "const gmailData = {\n  'ibrahim@microsoft.co': [\n    {\n      subject: 'Re: SolSombra Proposal - Microsoft Africa Digital Transformation',\n      date: '2026-04-25',\n      snippet: 'Thanks for sending this over. I have shared with the CFO and we should have feedback by end of next week. The phased approach looks promising.'\n    },\n    {\n      subject: 'Following up on our discovery call',\n      date: '2026-04-18',\n      snippet: 'Great conversation yesterday. As discussed, the main blocker is internal budget approval. I am pushing for Q2 sign-off.'\n    },\n    {\n      subject: 'Introduction - SolSombra x Microsoft Africa',\n      date: '2026-04-10',\n      snippet: 'Following our LinkedIn connection I wanted to introduce what we do at SolSombra and explore if there is a fit.'\n    }\n  ],\n  'fatima@credpal.africa': [\n    {\n      subject: 'Contract sign-off update',\n      date: '2026-04-26',\n      snippet: 'Board meeting is next Thursday. I will push for contract to be on the agenda. Should have a clear answer by Friday.'\n    },\n    {\n      subject: 'Re: Onboarding timeline',\n      date: '2026-04-20',\n      snippet: 'Assuming sign-off comes through, we are targeting May 15 start. Can you confirm your availability for the kickoff session?'\n    },\n    {\n      subject: 'CredPal x SolSombra - next steps',\n      date: '2026-04-14',\n      snippet: 'Really glad we connected. The operations audit scope makes sense. Legal is reviewing the agreement now.'\n    }\n  ],\n  'lara.okeke@medicorehealth.com': [\n    {\n      subject: 'Re: Revised proposal - MediCore Operations Overhaul',\n      date: '2026-04-27',\n      snippet: 'Grace and I have reviewed. Main concern is the 12-week timeline for phase 1. Can we discuss options to compress this?'\n    },\n    {\n      subject: 'Proposal feedback from Grace',\n      date: '2026-04-22',\n      snippet: 'Sharing Grace\\'s notes \u2014 she is supportive overall but wants clarity on the data migration component before sign-off.'\n    },\n    {\n      subject: 'MediCore intro call recap',\n      date: '2026-04-15',\n      snippet: 'Great to meet you both. Scope confirmed \u2014 3 departments, starting with operations. Will send formal proposal this week.'\n    }\n  ],\n  'grace.eze@healthlink.com': [\n    {\n      subject: 'Data sharing component - HealthLink angle',\n      date: '2026-04-23',\n      snippet: 'From HealthLink\\'s side the data interoperability piece is critical. We need this scoped separately before we can commit.'\n    },\n    {\n      subject: 'Re: MediCore partnership structure',\n      date: '2026-04-16',\n      snippet: 'Happy to be part of this engagement. HealthLink\\'s role needs to be defined clearly \u2014 we are a partner not a subcontractor here.'\n    },\n    {\n      subject: 'Introduction via Lara',\n      date: '2026-04-10',\n      snippet: 'Good to connect. Looking forward to understanding how SolSombra can support the HealthLink side of this engagement.'\n    }\n  ]\n};\n\nreturn $input.all().map(item => {\n  const input = item.json;\n  const email = input.primaryAttendee?.email ?? input.attendeeEmail ?? '';\n\n  const threads = gmailData[email] ?? [];\n\n  return {\n    json: {\n      _type: 'gmail',\n      attendeeEmail: email,\n      meetingTitle: input.meetingTitle,\n      meetingTime: input.meetingTime,\n      meetingType: input.meetingType,\n      isRecurring: input.isRecurring,\n      isGroup: input.isGroup ?? false,\n      allExternalAttendees: input.allExternalAttendees ?? [],\n      primaryAttendee: input.primaryAttendee,\n      threads: threads,\n      noThreadsFound: threads.length === 0,\n      source: 'mock_gmail'\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        784,
        864
      ],
      "id": "fbb39e8a-32a8-496b-bd7a-8741c77481c2",
      "name": "Get last 3 email threads simulation"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        416,
        224
      ],
      "id": "c63045c9-4ab6-458b-aaec-066a5b060857",
      "name": "combine"
    },
    {
      "parameters": {
        "jsCode": "// Get meeting context from combine\nconst meetingItems = $('combine').all();\n\n// Build a map of email -> meeting context for quick lookup\nconst meetingByEmail = {};\nmeetingItems.forEach(m => {\n  const email = m.json.primaryAttendee?.email?.toLowerCase();\n  if (email) meetingByEmail[email] = m.json;\n});\n\n// Get all attendee emails we care about\nconst attendeeEmails = Object.keys(meetingByEmail);\n\n// Contact GET returns duplicated results \u2014 use first item only, deduplicate by id\nconst allResults = $input.all()[0].json.results ?? [];\nconst seen = new Set();\nconst uniqueContacts = allResults.filter(c => {\n  if (seen.has(c.id)) return false;\n  seen.add(c.id);\n  return true;\n});\n\n// Filter only attendee contacts and attach their meeting context\nconst matched = uniqueContacts\n  .filter(c => attendeeEmails.includes((c.properties?.email ?? '').toLowerCase()))\n  .map(c => {\n    const email = c.properties.email.toLowerCase();\n    const meeting = meetingByEmail[email];\n\n    return {\n      json: {\n        // Meeting context carried forward\n        meetingTitle: meeting.meetingTitle,\n        meetingTime: meeting.meetingTime,\n        meetingType: meeting.meetingType,\n        isRecurring: meeting.isRecurring,\n        isGroup: meeting.isGroup ?? false,\n        allExternalAttendees: meeting.allExternalAttendees ?? [],\n        primaryAttendee: meeting.primaryAttendee,\n\n        // Clean HubSpot contact\n        hubspotContact: {\n          hubspotId: c.id,\n          email: c.properties.email,\n          firstName: c.properties.firstname ?? '',\n          lastName: c.properties.lastname ?? '',\n          fullName: `${c.properties.firstname ?? ''} ${c.properties.lastname ?? ''}`.trim(),\n          jobTitle: c.properties.jobtitle ?? 'Unknown role',\n          company: c.properties.company ?? 'Unknown company',\n          lifecycleStage: c.properties.lifecyclestage ?? null,\n          lastContacted: c.properties.notes_last_contacted ?? null,\n          lastNoteUpdated: c.properties.notes_last_updated ?? null,\n          totalNotes: c.properties.num_notes ?? '0',\n          hubspotUrl: c.url ?? null,\n          inHubspot: true\n        }\n      }\n    };\n  });\n\nreturn matched;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1008,
        560
      ],
      "id": "adb56718-ea9a-411d-aa3f-ea77d5ad75d8",
      "name": "Map contact email to Hubspot ID"
    },
    {
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/deals?limit=100&properties=dealname,dealstage,amount,closedate&associations=contacts",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "authorization",
              "value": "Bearer YOUR_TOKEN"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        784,
        720
      ],
      "id": "00f0bbd9-ac53-4f63-bcdf-f6d152f68c13",
      "name": "Get deal stage and info"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1248,
        576
      ],
      "id": "5859aaba-e743-4dd9-9f61-3e9b932f28e9",
      "name": "Merge1"
    },
    {
      "parameters": {
        "jsCode": "const DEAL_STAGES = {\n  'decisionmakerboughtin': 'New Lead',\n  'appointmentscheduled': 'Discovery Call Booked',\n  'qualifiedtobuy': 'Proposal Sent',\n  'presentationscheduled': 'Negotiation',\n  'contractsent': 'Won',\n  'closedwon': 'Closed Won',\n  'closedlost': 'Closed Lost',\n  'stage_0': 'Lost'\n};\n\nconst allInputs = $input.all();\n\n// Separate by shape \u2014 contacts have hubspotContact, deals have results array\nconst contactItems = allInputs.filter(i => i.json.hubspotContact !== undefined);\nconst dealItems = allInputs.filter(i => i.json.results !== undefined);\n\n// Flatten and deduplicate deals\nconst seen = new Set();\nconst deals = [];\nfor (const item of dealItems) {\n  for (const d of (item.json.results ?? [])) {\n    if (seen.has(d.id) || !d.properties?.dealname) continue;\n    seen.add(d.id);\n    deals.push({\n      dealName: d.properties.dealname,\n      dealNameLower: d.properties.dealname.toLowerCase(),\n      dealStage: DEAL_STAGES[d.properties.dealstage] ?? d.properties.dealstage,\n      amount: d.properties.amount ? `$${d.properties.amount}` : null,\n      closeDate: d.properties.closedate\n        ? new Date(d.properties.closedate).toLocaleDateString('en-GB')\n        : null,\n      contactIds: (d.associations?.contacts?.results ?? []).map(r => r.id)\n    });\n  }\n}\n\nreturn contactItems.map(item => {\n  const contact = item.json.hubspotContact;\n  const id = contact?.hubspotId;\n  const first = (contact?.firstName ?? '').toLowerCase();\n  const last = (contact?.lastName ?? '').toLowerCase();\n\n  const deal = deals.find(d => id && d.contactIds.includes(id))\n    ?? deals.find(d => d.dealNameLower.includes(first) || d.dealNameLower.includes(last))\n    ?? null;\n\n  return {\n    json: {\n      meetingTitle: item.json.meetingTitle,\n      meetingTime: item.json.meetingTime,\n      meetingType: item.json.meetingType,\n      isRecurring: item.json.isRecurring,\n      isGroup: item.json.isGroup ?? false,\n      allExternalAttendees: item.json.allExternalAttendees ?? [],\n      primaryAttendee: item.json.primaryAttendee,\n      hubspotContact: contact,\n      hubspotDeal: deal ? {\n        dealName: deal.dealName,\n        dealStage: deal.dealStage,\n        amount: deal.amount,\n        closeDate: deal.closeDate\n      } : { note: 'No active deal found for this contact' }\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        368
      ],
      "id": "2bc345ca-d88b-4c39-8141-f2b584b38418",
      "name": "Get final Hubspot CRM record, deal and company"
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "getAll",
        "team": "YOUR_TEAM",
        "space": "YOUR_SPACE",
        "folder": "YOUR_FOLDER",
        "list": "YOUR_LIST",
        "filters": {}
      },
      "type": "n8n-nodes-base.clickUp",
      "typeVersion": 1,
      "position": [
        784,
        224
      ],
      "id": "1547f937-e3ff-49b6-bb1e-382702f4d4e8",
      "name": "Get all task on clickup",
      "executeOnce": true,
      "credentials": {
        "clickUpOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const meetingItems = $('combine').all();\nconst clickupTasks = $input.all();\n\nreturn meetingItems.map(meetingItem => {\n  const meeting = meetingItem.json;\n  const primaryEmail = meeting.primaryAttendee?.email?.toLowerCase() ?? '';\n\n  // Get first name from email for tight keyword match\n  const namePart = primaryEmail.split('@')[0].toLowerCase();\n  const nameParts = namePart.split('.').filter(p => p.length > 2);\n\n  // Match by assignee email first\n  const assigneeMatched = clickupTasks.filter(task =>\n    (task.json.assignees ?? []).some(a =>\n      (a.email ?? '').toLowerCase() === primaryEmail\n    )\n  );\n\n  // Only keyword match if no assignee match found\n  // AND keyword must match the attendee's own name \u2014 not meeting title\n  const keywordMatched = assigneeMatched.length === 0\n    ? clickupTasks.filter(task => {\n        const taskName = (task.json.name ?? '').toLowerCase();\n        return nameParts.some(p => taskName.includes(p));\n      })\n    : [];\n\n  const matched = [...assigneeMatched, ...keywordMatched].map(t => ({\n    taskName: t.json.name,\n    status: t.json.status?.status ?? 'unknown',\n    dueDate: t.json.due_date\n      ? new Date(parseInt(t.json.due_date)).toLocaleDateString('en-GB')\n      : null,\n    assignees: (t.json.assignees ?? []).map(a => a.email).filter(Boolean)\n  }));\n\n  return {\n    json: {\n      _type: 'clickup',\n      attendeeEmail: primaryEmail,\n      meetingTitle: meeting.meetingTitle,\n      meetingTime: meeting.meetingTime,\n      meetingType: meeting.meetingType,\n      isRecurring: meeting.isRecurring,\n      isGroup: meeting.isGroup ?? false,\n      allExternalAttendees: meeting.allExternalAttendees ?? [],\n      primaryAttendee: meeting.primaryAttendee,\n      openTasks: matched,\n      noTasksFound: matched.length === 0,\n      source: 'clickup_live'\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1008,
        224
      ],
      "id": "076203d7-6d40-43bb-9483-a5021a0b1a8d",
      "name": "get only task with attendees involved"
    },
    {
      "parameters": {
        "jsCode": "const linkedinData = {\n  'ibrahim@microsoft.co': {\n    headline: 'CTO at Microsoft Africa | Digital Transformation | Cloud Infrastructure',\n    recentPosts: [\n      'Excited to be speaking at Lagos Tech Summit next week on AI in enterprise settings \u2014 the conversation around responsible adoption is overdue.',\n      'Microsoft Africa just crossed 1M cloud users. Proud of the team building infrastructure that actually works for this continent.'\n    ],\n    profileUrl: 'https://linkedin.com/in/ibrahim-lawal-mock',\n    note: 'Retrieved via Apify LinkedIn scraper \u2014 production implementation'\n  },\n  'fatima@credpal.africa': {\n    headline: 'COO at CredPal | Fintech Operations | Credit Access in Africa',\n    recentPosts: [\n      'CredPal closes Series B \u2014 we are building the credit infrastructure Africa deserves, one product at a time.',\n      'Great panel yesterday on embedded finance and what it actually means for SMEs trying to access working capital.'\n    ],\n    profileUrl: 'https://linkedin.com/in/fatima-abubakar-mock',\n    note: 'Retrieved via Apify LinkedIn scraper \u2014 production implementation'\n  },\n  'lara.okeke@medicorehealth.com': {\n    headline: 'COO at MediCore Health | Healthcare Operations | Systems Transformation',\n    recentPosts: [\n      'Healthcare operations in Nigeria \u2014 the challenges nobody in the boardroom wants to talk about but everyone in the field lives with daily.',\n      'Proud to announce MediCore expanding to 3 new states this quarter. The team has worked incredibly hard to make this happen.'\n    ],\n    profileUrl: 'https://linkedin.com/in/lara-okeke-mock',\n    note: 'Retrieved via Apify LinkedIn scraper \u2014 production implementation'\n  },\n  'grace.eze@healthlink.com': {\n    headline: 'Partnership Lead at HealthLink Systems | Health Data | Interoperability',\n    recentPosts: [\n      'Data sharing in healthcare \u2014 why interoperability is not just a technical problem but a trust problem that technology alone cannot solve.',\n      'Fascinating discussion at the Lagos Health Summit on what real patient data portability looks like in practice.'\n    ],\n    profileUrl: 'https://linkedin.com/in/grace-eze-mock',\n    note: 'Retrieved via Apify LinkedIn scraper \u2014 production implementation'\n  }\n};\n\nreturn $input.all().map(item => {\n  const input = item.json;\n  const email = input.primaryAttendee?.email ?? input.attendeeEmail ?? '';\n  \n  const data = linkedinData[email] ?? {\n    headline: null,\n    recentPosts: [],\n    profileUrl: null,\n    note: 'LinkedIn data unavailable \u2014 no URL stored in HubSpot contact record'\n  };\n\n  return {\n    json: {\n      _type: 'linkedin',\n      attendeeEmail: email,\n      meetingTitle: input.meetingTitle,\n      meetingTime: input.meetingTime,\n      meetingType: input.meetingType,\n      isRecurring: input.isRecurring,\n      isGroup: input.isGroup ?? false,\n      allExternalAttendees: input.allExternalAttendees ?? [],\n      primaryAttendee: input.primaryAttendee,\n      headline: data.headline,\n      recentPosts: data.recentPosts,\n      profileUrl: data.profileUrl,\n      noLinkedinData: !data.headline,\n      source: 'mock_apify'\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        784,
        1024
      ],
      "id": "2d8aeb68-42b2-43cc-bce8-afe87f00d43d",
      "name": "LinkedIn lookup API simulation via Apify"
    },
    {
      "parameters": {
        "numberInputs": 6
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1472,
        -80
      ],
      "id": "fa050f71-a6e0-416a-8d4b-763869f2e242",
      "name": "Merge2"
    },
    {
      "parameters": {
        "jsCode": "const allInputs = $input.all();\n\nconst hubspotItems = allInputs.filter(i => i.json.hubspotContact !== undefined);\nconst fyxerItems = allInputs.filter(i => i.json._type === 'fyxer');\nconst gmailItems = allInputs.filter(i => i.json._type === 'gmail');\nconst clickupItems = allInputs.filter(i => i.json._type === 'clickup');\nconst linkedinItems = allInputs.filter(i => i.json._type === 'linkedin');\nconst internalItems = allInputs.filter(i =>\n  i.json.meetingType === 'internal' &&\n  i.json.hubspotContact === undefined &&\n  i.json._type === undefined\n);\n\n// Format meeting time cleanly: \"9:00 AM\" or \"12:00 PM\"\nconst formatTime = (isoString) => {\n  if (!isoString) return 'Time not set';\n  const date = new Date(isoString);\n  return date.toLocaleTimeString('en-GB', {\n    hour: 'numeric',\n    minute: '2-digit',\n    hour12: true,\n    timeZone: 'Africa/Lagos'\n  }).toUpperCase();\n};\n\nconst meetingMap = {};\n\nhubspotItems.forEach(item => {\n  const email = item.json.primaryAttendee?.email;\n  if (!email) return;\n  if (!meetingMap[email]) meetingMap[email] = {};\n  Object.assign(meetingMap[email], {\n    meetingTitle: item.json.meetingTitle,\n    meetingTime: formatTime(item.json.meetingTime),\n    meetingType: item.json.meetingType,\n    isRecurring: item.json.isRecurring,\n    isGroup: item.json.isGroup ?? false,\n    allExternalAttendees: item.json.allExternalAttendees ?? [],\n    primaryAttendee: item.json.primaryAttendee,\n    hubspotContact: item.json.hubspotContact,\n    hubspotDeal: item.json.hubspotDeal\n  });\n});\n\nfyxerItems.forEach(item => {\n  const email = item.json.attendeeEmail;\n  if (!email || !meetingMap[email]) return;\n  meetingMap[email].lastMeeting = item.json.summary\n    ? { date: item.json.lastMeetingDate, summary: item.json.summary, actionItems: item.json.actionItems ?? [] }\n    : null;\n});\n\ngmailItems.forEach(item => {\n  const email = item.json.attendeeEmail;\n  if (!email || !meetingMap[email]) return;\n  meetingMap[email].emailThreads = (item.json.threads ?? []).length > 0 ? item.json.threads : null;\n});\n\nclickupItems.forEach(item => {\n  const email = item.json.attendeeEmail;\n  if (!email || !meetingMap[email]) return;\n  meetingMap[email].openTasks = (item.json.openTasks ?? []).length > 0 ? item.json.openTasks : null;\n});\n\nlinkedinItems.forEach(item => {\n  const email = item.json.attendeeEmail;\n  if (!email || !meetingMap[email]) return;\n  meetingMap[email].linkedin = item.json.headline\n    ? { headline: item.json.headline, recentPosts: item.json.recentPosts ?? [] }\n    : null;\n});\n\nconst meetingGroups = {};\nObject.values(meetingMap).forEach(m => {\n  const key = m.meetingTitle + m.meetingTime;\n  if (!meetingGroups[key]) {\n    meetingGroups[key] = {\n      meetingTitle: m.meetingTitle,\n      meetingTime: m.meetingTime,\n      meetingType: m.meetingType,\n      isRecurring: m.isRecurring,\n      isGroup: m.isGroup,\n      attendees: []\n    };\n  }\n  meetingGroups[key].attendees.push({\n    name: m.hubspotContact?.fullName ?? m.primaryAttendee?.email,\n    email: m.primaryAttendee?.email,\n    jobTitle: m.hubspotContact?.jobTitle ?? null,\n    company: m.hubspotContact?.company ?? null,\n    lifecycleStage: m.hubspotContact?.lifecycleStage ?? null,\n    deal: m.hubspotDeal?.note ? null : m.hubspotDeal,\n    lastMeeting: m.lastMeeting ?? null,\n    emailThreads: m.emailThreads ?? null,\n    openTasks: m.openTasks ?? null,\n    linkedin: m.linkedin ?? null\n  });\n});\n\nconst internalMeetings = internalItems.map(i => ({\n  meetingTitle: i.json.meetingTitle,\n  meetingTime: formatTime(i.json.meetingTime),\n  meetingType: 'internal',\n  isRecurring: i.json.isRecurring,\n  attendees: (i.json.attendees ?? []).map(a => ({\n    name: a.name,\n    email: a.email,\n    openTasks: (a.openTasks ?? []).length > 0 ? a.openTasks : null,\n    lastMeeting: a.lastMeeting?.summary && a.lastMeeting.summary !== 'No previous meeting summary found'\n      ? a.lastMeeting\n      : null\n  }))\n}));\n\n// Sort all meetings by time\nconst externalMeetings = Object.values(meetingGroups).sort((a, b) =>\n  a.meetingTime.localeCompare(b.meetingTime)\n);\n\nconst allMeetingsSorted = [\n  ...internalMeetings,\n  ...externalMeetings\n].sort((a, b) => a.meetingTime.localeCompare(b.meetingTime));\n\nreturn [{\n  json: {\n    date: new Date().toLocaleDateString('en-GB', {\n      weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'\n    }),\n    totalMeetings: externalMeetings.length + internalMeetings.length,\n    internalMeetings,\n    externalMeetings,\n    allMeetingsSorted\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1744,
        -16
      ],
      "id": "8ef48e94-b1b6-4356-85ff-a63d6f51f8da",
      "name": "Prepare data for AI"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2160,
        -16
      ],
      "id": "3a8e22db-1fec-4cb9-b3f2-044fb7cf432d",
      "name": "Claude generates brief",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "YOUR_EMAIL",
        "subject": "={{ 'Meeting Prep for ' + $json.date + ' | ' + $json.totalMeetings + ' meetings' }}",
        "message": "={{ $json.html }}",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        2752,
        -16
      ],
      "id": "be21aa85-d9e1-4875-a098-201229104dad",
      "name": "Send a message",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst raw = input.content[0].text;\nconst cleaned = raw.replace(/```json|```/g, '').trim();\n\nlet meetings;\ntry {\n  meetings = JSON.parse(cleaned);\n} catch(e) {\n  throw new Error('Failed to parse Claude JSON: ' + raw.substring(0, 200));\n}\n\n// Pull date and totalMeetings from an earlier node directly\nconst sourceData = $('Prepare data for AI').first().json;\n\nreturn [{\n  json: {\n    meetings,\n    date: sourceData.date,\n    totalMeetings: sourceData.totalMeetings\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2368,
        -16
      ],
      "id": "a8186204-5c30-4d07-8dc1-76e5ca649807",
      "name": "Extract meeting brief in json"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst m = data.meetings;\n\nconst section = (title, list) => {\n  if (!list || list.length === 0) return '';\n  \n  const colors = {\n    'Internal Meetings': '#4A90D9',\n    'External Meetings': '#7B68EE',\n    'Group Meetings': '#50C878'\n  };\n  const accent = colors[title] || '#888';\n\n  const heading = `\n    <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 20px;\">\n      <tr>\n        <td width=\"3\" style=\"background:${accent};border-radius:2px;\">&nbsp;</td>\n        <td style=\"padding-left:10px;font-weight:700;font-size:12px;text-transform:uppercase;letter-spacing:1.2px;color:${accent};\">${title}</td>\n      </tr>\n    </table>`;\n\n  const items = list.map((meeting, i) => {\n    const isLast = i === list.length - 1;\n    return `\n    <div style=\"margin-bottom:${isLast ? '8' : '22'}px;padding-bottom:${isLast ? '0' : '22'}px;border-bottom:${isLast ? 'none' : '1px solid #ebebeb'};\">\n      <p style=\"margin:0 0 7px;font-size:16px;font-weight:700;color:#111;\">${meeting.time} <span style=\"color:#ccc;font-weight:400;\">|</span> ${meeting.title}</p>\n      <p style=\"margin:0;color:#444;line-height:1.9;font-size:14.5px;\">\n        <span style=\"color:#999;font-style:italic;\">${meeting.points[0]}</span>\n        ${meeting.points.slice(1).join(' ')}\n      </p>\n    </div>`;\n  }).join('');\n\n  return `\n  <div style=\"margin-bottom:20px;background:#F7F8FA;border:1px solid #e2e4e8;border-radius:8px;padding:22px 26px;\">\n    ${heading}\n    ${items}\n  </div>`;\n};\n\nconst html = `<!DOCTYPE html>\n<html>\n<head><meta charset=\"UTF-8\"></head>\n<body style=\"font-family:'Helvetica Neue',Arial,sans-serif;font-size:15px;color:#1a1a1a;line-height:1.85;max-width:680px;margin:0 auto;padding:24px;background:#EDEEF0;\">\n\n  <div style=\"background:#F7F8FA;border:1px solid #e2e4e8;border-radius:8px;padding:22px 26px;margin-bottom:16px;\">\n    <p style=\"margin:0 0 4px;font-size:12px;color:#999;text-transform:uppercase;letter-spacing:0.8px;\">Meeting Prep Brief</p>\n    <p style=\"margin:0;font-size:24px;font-weight:700;color:#111;\">${data.date}</p>\n    <p style=\"margin:5px 0 0;font-size:14px;color:#888;\">${data.totalMeetings} meetings today</p>\n  </div>\n\n  <div style=\"background:#F7F8FA;border:1px solid #e2e4e8;border-radius:8px;padding:20px 26px;margin-bottom:20px;\">\n    <p style=\"margin:0;font-size:15px;color:#333;line-height:1.8;\">Hi Landon, here is your meeting brief for the day. You have <strong>${data.totalMeetings} meetings</strong> scheduled. Kindly review the context and talking points below before your day starts.</p>\n  </div>\n\n  ${section('Internal Meetings', m.internal)}\n  ${section('External Meetings', m.external)}\n  ${section('Group Meetings', m.group)}\n\n  <p style=\"margin-top:12px;font-size:12px;color:#bbb;text-align:center;\">Prepared by SolSombra Meeting Prep Automation</p>\n\n</body>\n</html>`;\n\nreturn [{ json: { html, date: data.date, totalMeetings: data.totalMeetings } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2560,
        -16
      ],
      "id": "40d65d7d-aa69-4955-879c-8dd268323a05",
      "name": "Arrange"
    },
    {
      "parameters": {
        "content": "## TRIGGER & SCHEDULING",
        "height": 272,
        "width": 624
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1136,
        0
      ],
      "typeVersion": 1,
      "id": "21b06eda-737f-4920-9565-8d19c44088fc",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## MEETING GATE & CLASSIFICATION",
        "height": 416,
        "width": 496,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        -64
      ],
      "typeVersion": 1,
      "id": "182677b6-21ea-4821-acdd-527216cde005",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## TASK & CALENDAR ENRICHMENT FOR INTERNAL TEAM",
        "height": 384,
        "width": 848,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -192
      ],
      "typeVersion": 1,
      "id": "de54ca7e-6a68-4b50-b8ba-aa75edf3ae66",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## ROUTING LOGIC FOR GROUP AND EXTERNAL MEETINGS",
        "height": 448,
        "width": 512,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        208
      ],
      "typeVersion": 1,
      "id": "1f5ddcb2-be73-4bc8-bd23-b28cb1987c9a",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## AI BRIEF GENERATION & DELIVERY",
        "height": 288,
        "width": 1056,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1888,
        -96
      ],
      "typeVersion": 1,
      "id": "88425630-c286-43bd-874a-fcc2e93c9f44",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## CONTACT ENRICHMENT & EXTERNAL CONTEXT\n\n\n",
        "height": 1072,
        "width": 992,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        208
      ],
      "typeVersion": 1,
      "id": "38286299-f277-4963-8fc1-c9f531713d11",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "content": "## DATA PREPARATION",
        "height": 304,
        "width": 992,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        -112
      ],
      "typeVersion": 1,
      "id": "18a900aa-c118-4f21-acf7-bddb69ca67c1",
      "name": "Sticky Note6"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -1104,
        304
      ],
      "id": "0fad9f85-edc7-4981-b890-c2526bcd9617",
      "name": "Schedule Trigger",
      "disabled": true
    }
  ],
  "connections": {
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Fetch all meetings for the next day",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch all meetings for the next day": {
      "main": [
        [
          {
            "node": "Filter real meeting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter real meeting": {
      "main": [
        [
          {
            "node": "Are there no meetings today?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Are there no meetings today?": {
      "main": [
        [
          {
            "node": "Let founder know that there are no meetings",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Get many tasks",
            "type": "main",
            "index": 0
          },
          {
            "node": "Simulate Fyxer API to get meeting summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tag as group + split to one item per external attendee",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Standardize external data route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many tasks": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simulate Fyxer API to get meeting summary": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Match internal team members to their task and meeting summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match internal team members to their task and meeting summaries": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Standardize prompt for Claude": {
      "main": [
        [
          {
            "node": "Claude generates brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tag as group + split to one item per external attendee": {
      "main": [
        [
          {
            "node": "combine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Standardize external data route": {
      "main": [
        [
          {
            "node": "combine",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get Hubspot Contact record": {
      "main": [
        [
          {
            "node": "Map contact email to Hubspot ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "combine": {
      "main": [
        [
          {
            "node": "Fyxer meeting summary simulation",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get last 3 email threads simulation",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Hubspot Contact record",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get all task on clickup",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get deal stage and info",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn lookup API simulation via Apify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map contact email to Hubspot ID": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get last 3 email threads simulation": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Get deal stage and info": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "Get final Hubspot CRM record, deal and company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all task on clickup": {
      "main": [
        [
          {
            "node": "get only task with attendees involved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get only task with attendees involved": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Fyxer meeting summary simulation": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Get final Hubspot CRM record, deal and company": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "LinkedIn lookup API simulation via Apify": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "Prepare data for AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare data for AI": {
      "main": [
        [
          {
            "node": "Standardize prompt for Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude generates brief": {
      "main": [
        [
          {
            "node": "Extract meeting brief in json",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract meeting brief in json": {
      "main": [
        [
          {
            "node": "Arrange",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Arrange": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch all meetings for the next day",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "YOUR_VERSION_ID",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "YOUR_WORKFLOW_ID",
  "tags": []
}