{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "2edbdfc7-3e64-4f1e-8642-335a3fdb1a39",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2304,
        -528
      ],
      "parameters": {
        "color": 4,
        "width": 572,
        "height": 1108,
        "content": "## Client Meeting Recap Email \u2014 Fireflies + GPT-4o-mini + Gmail + Sheets\n\nFor account managers, consultants, and agency owners who want a polished client recap email sent automatically after every meeting. When Fireflies finishes transcribing a meeting, this workflow detects the client email from participants, uses GPT-4o-mini to write a warm 250-word professional recap, sends it via Gmail with your reply-to address, and logs every sent recap to Google Sheets. The client receives a genuine-sounding email \u2014 no AI footprint, no Fireflies links, just a professional summary of what was discussed and what happens next.\n\n## How it works\n- **1. Webhook \u2014 Fireflies Transcript Done** receives the meetingId from Fireflies on transcription complete\n- **2. Set \u2014 Config Values** stores Fireflies API key, sender details, company name, Sheet ID, and client email detection settings\n- **3. HTTP \u2014 Fetch Transcript** calls the Fireflies GraphQL API for title, date, participants, transcript URL, overview, keywords, action items, gist, and bullet points\n- **4. Code \u2014 Extract Data and Detect Client Email** detects the client email using three strategies: find a participant whose domain differs from yourEmailDomain, extract an email embedded in the meeting title, or fall back to defaultClientEmail\n- **5. AI Agent \u2014 Write Recap Email** uses GPT-4o-mini (temperature 0.6) to write a warm professional recap with greeting, key points, action items, and sign-off\n- **7. Code \u2014 Prepare Email Fields** reads the AI output and builds the email subject and all fields for sending and logging\n- **8. Gmail \u2014 Send Recap Email** sends the recap to the detected client email with your address as reply-to\n- **9. Google Sheets \u2014 Log Sent Recaps** appends one row per sent recap for tracking\n\n## Set up steps\n1. Activate the workflow and copy the webhook URL from node 1\n2. In Fireflies \u2014 go to Settings, Developer Settings, Webhooks, and paste the URL\n3. In **2. Set \u2014 Config Values** \u2014 replace all seven values: Fireflies API key, sender name, sender email, company name, Google Sheet ID, default client email, and your company domain\n4. In **6. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n5. In **8. Gmail \u2014 Send Recap Email** \u2014 connect your Gmail OAuth2 credential using the account that matches senderEmail\n6. In **9. Google Sheets \u2014 Log Sent Recaps** \u2014 connect your Google Sheets OAuth2 credential\n7. Create a Google Sheet tab named Recap Email Log with columns: Date, Meeting Title, Client Email, Email Subject, Status, Fireflies URL, Logged At"
      },
      "typeVersion": 1
    },
    {
      "id": "f321e34f-175a-4006-af8c-038b4ed1a96a",
      "name": "Section \u2014 Webhook, Config, and Transcript Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1648,
        -336
      ],
      "parameters": {
        "color": 5,
        "width": 628,
        "height": 532,
        "content": "## Webhook, Config, and Transcript Fetch\nFireflies POSTs a meetingId here on transcription complete. Config stores sender details, client email fallback, and your company domain for smart client detection. HTTP fetches the full Fireflies summary \u2014 no full sentences needed."
      },
      "typeVersion": 1
    },
    {
      "id": "04db0f0c-e2d1-427d-ba2d-98df196307a1",
      "name": "Section \u2014 Client Email Detection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 360,
        "height": 436,
        "content": "## Client Email Detection\nThree-strategy detection: first looks for a participant whose email domain differs from yourEmailDomain, then checks if the meeting title contains an email, then falls back to defaultClientEmail. Also extracts client name from email format."
      },
      "typeVersion": 1
    },
    {
      "id": "6ff473c5-c8ea-4768-a733-3c51ccbe171e",
      "name": "Section \u2014 AI Recap Email Writing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -480
      ],
      "parameters": {
        "color": 6,
        "width": 376,
        "height": 820,
        "content": "## AI Recap Email Writing\nGPT-4o-mini (temperature 0.6) writes a warm 250-word professional recap. Structure: greeting, thank you opener, key points as bullets, action items, closing, sign-off. Plain text only \u2014 no AI mention, no Fireflies links."
      },
      "typeVersion": 1
    },
    {
      "id": "f6ffaeb4-7660-40d4-909a-fa855f98246f",
      "name": "Section \u2014 Email Assembly and Parallel Delivery",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -384
      ],
      "parameters": {
        "color": 4,
        "width": 660,
        "height": 692,
        "content": "## Email Assembly and Parallel Delivery\nBuilds email subject and assembles all fields. Gmail sends the recap to the client with your email as reply-to so replies come directly to you. Google Sheets logs every sent recap simultaneously."
      },
      "typeVersion": 1
    },
    {
      "id": "170feb32-f026-466a-822a-9369d312e1e5",
      "name": "1. Webhook \u2014 Fireflies Transcript Done",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1600,
        -96
      ],
      "parameters": {
        "path": "fireflies-recap-email",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1.1
    },
    {
      "id": "77602922-8f20-46c4-aade-8fb5b12ca04c",
      "name": "2. Set \u2014 Config Values",
      "type": "n8n-nodes-base.set",
      "position": [
        -1392,
        -96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "firefliesApiKey",
              "type": "string",
              "value": "YOUR_FIREFLIES_API_KEY"
            },
            {
              "id": "cfg-002",
              "name": "senderName",
              "type": "string",
              "value": "YOUR FULL NAME"
            },
            {
              "id": "cfg-003",
              "name": "senderEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cfg-004",
              "name": "companyName",
              "type": "string",
              "value": "YOUR COMPANY NAME"
            },
            {
              "id": "cfg-005",
              "name": "sheetId",
              "type": "string",
              "value": "YOUR_GOOGLE_SHEET_ID"
            },
            {
              "id": "cfg-006",
              "name": "sheetName",
              "type": "string",
              "value": "Recap Email Log"
            },
            {
              "id": "cfg-007",
              "name": "defaultClientEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cfg-008",
              "name": "yourEmailDomain",
              "type": "string",
              "value": "yourcompany.com"
            },
            {
              "id": "cfg-009",
              "name": "meetingId",
              "type": "string",
              "value": "={{ $json.meetingId || $json.body?.meetingId || $json.data?.meetingId || '' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3bfdec37-b20e-4f0e-b170-61b8b93a9b83",
      "name": "3. HTTP \u2014 Fetch Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1152,
        -96
      ],
      "parameters": {
        "url": "https://api.fireflies.ai/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"query GetTranscript($id: String!) { transcript(id: $id) { id title date duration participants host_email transcript_url summary { overview keywords action_items gist bullet_gist } } }\",\n  \"variables\": {\n    \"id\": \"{{ $json.meetingId }}\"\n  }\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": "636bcc64-6451-483c-bf27-247ec441d2df",
      "name": "4. Code \u2014 Extract Data and Detect Client Email",
      "type": "n8n-nodes-base.code",
      "position": [
        -848,
        -96
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst config = $('2. Set \u2014 Config Values').item.json;\n\nconst t = response?.data?.transcript;\nif (!t || !t.id) throw new Error('Transcript not found for meetingId: ' + config.meetingId);\n\nconst summary = t.summary || {};\nconst participants = t.participants || [];\n\n// Smart client email detection\nconst yourDomain = (config.yourEmailDomain || '').toLowerCase().trim();\nlet clientEmail = config.defaultClientEmail;\nlet clientName = 'Valued Client';\n\n// Strategy 1: find external email from participants\nif (yourDomain) {\n  const externalParticipant = participants.find(p => {\n    const email = (p || '').toLowerCase();\n    return email.includes('@') && !email.includes(yourDomain);\n  });\n  if (externalParticipant) {\n    clientEmail = externalParticipant;\n    const emailPart = externalParticipant.split('@')[0];\n    clientName = emailPart\n      .replace(/[._-]/g, ' ')\n      .replace(/\\b\\w/g, c => c.toUpperCase())\n      .trim();\n  }\n}\n\n// Strategy 2: check if client email is embedded in meeting title\nconst titleEmailMatch = (t.title || '').match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/);\nif (titleEmailMatch) {\n  clientEmail = titleEmailMatch[0];\n}\n\n// Meeting metadata\nconst meetingTitle = t.title || 'Our Meeting';\nconst meetingDate = t.date\n  ? new Date(t.date).toLocaleDateString('en-GB', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' })\n  : new Date().toLocaleDateString('en-GB', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' });\nconst durationMin = Math.round((t.duration || 0) / 60);\nconst keywords = (summary.keywords || []).slice(0, 8).join(', ') || 'None';\nconst overview = (summary.overview || '').substring(0, 800);\nconst actionItems = (summary.action_items || []).join('\\n') || 'None noted';\nconst bulletPoints = (summary.bullet_gist || []).join('\\n') || '';\nconst gist = summary.gist || '';\n\nconst loggedAt = new Date().toISOString().replace('T', ' ').substring(0, 16);\n\nreturn [{\n  json: {\n    meetingId: t.id,\n    meetingTitle,\n    meetingDate,\n    durationMin,\n    clientEmail,\n    clientName,\n    participants: participants.join(', '),\n    transcriptUrl: t.transcript_url || 'Not available',\n    keywords,\n    overview,\n    actionItems,\n    bulletPoints,\n    gist,\n    loggedAt,\n    senderName: config.senderName,\n    senderEmail: config.senderEmail,\n    companyName: config.companyName,\n    sheetId: config.sheetId,\n    sheetName: config.sheetName\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ccfd982e-8a88-45ae-920d-2e2f725cb8dd",
      "name": "5. AI Agent \u2014 Write Recap Email",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -480,
        -96
      ],
      "parameters": {
        "text": "=You are a professional account manager at {{ $json.companyName }} writing a post-meeting recap email to a client.\n\nMEETING DETAILS:\nMeeting Title: {{ $json.meetingTitle }}\nDate: {{ $json.meetingDate }}\nDuration: {{ $json.durationMin }} minutes\nSent by: {{ $json.senderName }}, {{ $json.companyName }}\nClient Name: {{ $json.clientName }}\n\nFIREFLIES MEETING SUMMARY:\nGist: {{ $json.gist }}\nOverview: {{ $json.overview }}\nKey Points: {{ $json.bulletPoints }}\nTopics Discussed: {{ $json.keywords }}\n\nACTION ITEMS FROM MEETING:\n{{ $json.actionItems }}\n\nWrite a professional, warm recap email to send to the client after the meeting.\n\nEMAIL STRUCTURE:\n\nLine 1: Greeting using client name (Hi [name], or Dear [name],)\n\nParagraph 1 \u2014 Opening:\nThank the client for their time. Reference the meeting title or topic naturally. Keep it warm and genuine. 2 sentences maximum.\n\nParagraph 2 \u2014 What We Covered:\nSummarize the key points discussed in 3 to 4 bullet points using plain dashes. Be specific \u2014 use real content from the meeting summary. Not generic filler.\n\nParagraph 3 \u2014 Next Steps:\nList the action items clearly. Format each as: dash, action description, who is responsible. If no action items write one line: We will follow up shortly with next steps.\n\nParagraph 4 \u2014 Closing:\nOne warm closing sentence. Invite them to reach out with any questions.\n\nSign off:\nBest regards,\n[senderName]\n[companyName]\n\nRULES:\n- Plain text only. No markdown. No asterisks. No hashtags.\n- Professional but warm tone \u2014 not corporate or robotic.\n- Maximum 250 words total.\n- Do NOT mention that this was generated by AI.\n- Do NOT reveal internal notes or Fireflies links.\n- Write as if you personally attended the meeting and are writing this immediately after.",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "8e7aa762-e695-42f6-8538-834eeef4e524",
      "name": "6. OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -480,
        192
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 600,
          "temperature": 0.6
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ecece264-91cc-4902-a2d4-ba057a51646c",
      "name": "7. Code \u2014 Prepare Email Fields",
      "type": "n8n-nodes-base.code",
      "position": [
        -16,
        -96
      ],
      "parameters": {
        "jsCode": "const aiEmail = $input.first().json.output || '';\nconst data = $('4. Code \u2014 Extract Data and Detect Client Email').item.json;\n\nif (!aiEmail || aiEmail.length < 50) {\n  throw new Error('AI could not generate email body. Check OpenAI credentials.');\n}\n\nconst emailSubject = 'Meeting Recap \u2014 ' + data.meetingTitle + ' \u2014 ' + data.meetingDate;\n\nreturn [{\n  json: {\n    emailBody: aiEmail,\n    emailSubject,\n    clientEmail: data.clientEmail,\n    senderName: data.senderName,\n    senderEmail: data.senderEmail,\n    companyName: data.companyName,\n    meetingTitle: data.meetingTitle,\n    meetingDate: data.meetingDate,\n    transcriptUrl: data.transcriptUrl,\n    loggedAt: data.loggedAt,\n    sheetId: data.sheetId,\n    sheetName: data.sheetName\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ec475c83-b087-467c-9a29-84ab4fb83180",
      "name": "8. Gmail \u2014 Send Recap Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        320,
        -256
      ],
      "parameters": {
        "sendTo": "={{ $json.clientEmail }}",
        "message": "={{ $json.emailBody }}",
        "options": {
          "replyTo": "={{ $json.senderEmail }}",
          "appendAttribution": false
        },
        "subject": "={{ $json.emailSubject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "6cbb8ac9-639d-4b58-a7c4-88857768be03",
      "name": "9. Google Sheets \u2014 Log Sent Recaps",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        320,
        64
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.meetingDate }}",
            "Status": "Sent",
            "Logged At": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.loggedAt }}",
            "Client Email": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.clientEmail }}",
            "Email Subject": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.emailSubject }}",
            "Fireflies URL": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.transcriptUrl }}",
            "Meeting Title": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.meetingTitle }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {
          "cellFormat": "USER_ENTERED"
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.sheetName }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('7. Code \u2014 Prepare Email Fields').item.json.sheetId }}"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "connections": {
    "2. Set \u2014 Config Values": {
      "main": [
        [
          {
            "node": "3. HTTP \u2014 Fetch Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP \u2014 Fetch Transcript": {
      "main": [
        [
          {
            "node": "4. Code \u2014 Extract Data and Detect Client Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "5. AI Agent \u2014 Write Recap Email",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "7. Code \u2014 Prepare Email Fields": {
      "main": [
        [
          {
            "node": "8. Gmail \u2014 Send Recap Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "9. Google Sheets \u2014 Log Sent Recaps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. AI Agent \u2014 Write Recap Email": {
      "main": [
        [
          {
            "node": "7. Code \u2014 Prepare Email Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Webhook \u2014 Fireflies Transcript Done": {
      "main": [
        [
          {
            "node": "2. Set \u2014 Config Values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code \u2014 Extract Data and Detect Client Email": {
      "main": [
        [
          {
            "node": "5. AI Agent \u2014 Write Recap Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}