8
n8n 한국어amn8n.com

Fathom 회의 요약 및 행동 AI 에이전트

고급

이것은자동화 워크플로우로, 18개의 노드를 포함합니다.주로 If, Set, Code, Webhook, GoogleDrive 등의 노드를 사용하며. Gemini AI를 사용하여 Fathom 회의 기록을 구성화된 Google 문서 요약으로 변환

사전 요구사항
  • HTTP Webhook 엔드포인트(n8n이 자동으로 생성)
  • Google Drive API 인증 정보
  • 대상 API의 인증 정보가 필요할 수 있음
  • Google Gemini API Key

카테고리

-
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
  "id": "AJch0Bi1L5u67nRO",
  "meta": {
    "instanceId": "90dd23d886c9cb675f452d0fb004af6ee783e4e974ef4384cbfad1854c68a875",
    "templateCredsSetupCompleted": true
  },
  "name": "Fathom Meeting Summary & Actions AI Agent",
  "tags": [],
  "nodes": [
    {
      "id": "a362776f-d572-4983-9dc6-a161ddd30392",
      "name": "Google Gemini 채팅 모델",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        96,
        16
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "etClcv7ej0yswPTF",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "73dc3249-bc5d-4676-95e8-bcd4b2fea8e9",
      "name": "구조화된 출력 파서",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        272,
        16
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"Meeting Title\": \"Test call\",\n  \"Recording URL\": \"https://fathom.video/share/745cvtcxSXKb7YyfDMiyzMChZ3AsoGad\",\n  \"Scheduled Date/Time\": \"30-01-2025, 12:15 to 12:30\",\n  \"Recording Date/Time\": \"30-01-2025, 12:16 to 12:16\",\n  \"Attendees\": \"John Doe <johndoe@domain.com> (Attendee)\",\n  \"Executive Summary\": \"Single-speaker intro explaining that Fathom auto-records meetings, captures key moments and action items, and allows manual highlights. No concrete decisions or actions were stated.\",\n  \"Key Points\": \"Fathom auto-records and captures important moments and action items.\\nPost-meeting summaries are delivered quickly.\\nManual highlight button available for special moments.\",\n  \"Action Items\": \"Send John 2-3 specific YouTube video links.\\nGet automation engineer to review Sheets integration Mon-Tue.\",\n  \"Decisions\": \"Emmily to update feature on the contact page.\\nJohn will speak to Jess about product development roadmap.\",\n  \"Risks/Concerns\": \"John mentioned development pace is slow in January meaning a late release date is likely.\",\n  \"Open Questions\": \"What's the status of the Hubspot implementation?\\nNone identified in the transcript.\",\n  \"Entities — People\": \"Emmily Bowman; John Doe\",\n  \"Entities — Orgs/Places\": \"Fathom\",\n  \"Entities — Numbers/Dates\": \"30 seconds\"\n}\n"
      },
      "typeVersion": 1.3
    },
    {
      "id": "a4e520d6-c9bd-4060-94b4-e61f7ef38b19",
      "name": "핵심 부분 포맷팅",
      "type": "n8n-nodes-base.code",
      "position": [
        -368,
        -96
      ],
      "parameters": {
        "jsCode": "// Minimal formatter: merged transcript turns with per-line timestamps\n\nconst b = $json.body ?? $json ?? {};\nconst toStr = (v) => (typeof v === 'string' ? v.trim() : (v ?? '') + '');\nconst tsToSec = (ts) => {\n  const m = typeof ts === 'string' && ts.match(/^(\\d{2}):(\\d{2}):(\\d{2})$/);\n  if (!m) return 0;\n  const [, hh, mm, ss] = m;\n  return (+hh) * 3600 + (+mm) * 60 + (+ss);\n};\nconst uniqBy = (arr, keyFn) => {\n  const seen = new Set();\n  return arr.filter(x => {\n    const key = keyFn(x);\n    if (seen.has(key)) return false;\n    seen.add(key);\n    return true;\n  });\n};\n\n// ---- Attendees (optional) ----\nconst invitees = Array.isArray(b.calendar_invitees) ? b.calendar_invitees : [];\nconst attendeesRaw = [\n  ...invitees.map(i => ({\n    name: toStr(i.name) || toStr(i.matched_speaker_display_name) || 'Unknown',\n    email: toStr(i.email) || null,\n    role: 'Attendee',\n  })),\n  ...(b.recorded_by ? [{\n    name: toStr(b.recorded_by.name) || 'Unknown',\n    email: toStr(b.recorded_by.email) || null,\n    role: 'Host',\n  }] : []),\n];\nconst attendees = uniqBy(attendeesRaw, a => a.email ? `e:${a.email.toLowerCase()}` : `n:${a.name.toLowerCase()}`);\n\n// ---- Per-line → normalize + sort ----\nconst tItems = Array.isArray(b.transcript) ? b.transcript : [];\nconst lines = tItems\n  .map(i => ({\n    timestamp: toStr(i?.timestamp) || '00:00:00',\n    seconds: tsToSec(toStr(i?.timestamp)),\n    speaker: toStr(i?.speaker?.display_name) || 'Unknown',\n    email: toStr(i?.speaker?.matched_calendar_invitee_email) || null,\n    text: toStr(i?.text),\n  }))\n  .filter(x => x.text)\n  .sort((a, c) => a.seconds - c.seconds)\n  .map(({ seconds, ...rest }) => rest);\n\n// ---- Merge consecutive lines by the same person, but keep per-line timestamps ----\nconst norm = s => (s || '').trim().replace(/\\s+/g, ' ').toLowerCase();\nconst samePerson = (a, b) => {\n  const ae = (a.email || '').toLowerCase();\n  const be = (b.email || '').toLowerCase();\n  if (ae && be) return ae === be;\n  const an = norm(a.speaker);\n  const bn = norm(b.speaker);\n  if (!ae && !be && (an === 'unknown' || bn === 'unknown')) return false;\n  return an === bn;\n};\n\nconst transcript_merged = [];\nfor (const line of lines) {\n  const last = transcript_merged[transcript_merged.length - 1];\n  const entry = { ts: line.timestamp, text: line.text.trim() };\n\n  if (last && samePerson(last, line)) {\n    last.lines.push(entry);                 // keep per-line timestamps\n    last.texts.push(entry.text);            // backwards-compat (array of strings)\n    last.end_timestamp = line.timestamp;\n  } else {\n    transcript_merged.push({\n      speaker: line.speaker,\n      email: line.email || null,\n      start_timestamp: line.timestamp,\n      end_timestamp: line.timestamp,\n      start_seconds: tsToSec(line.timestamp),\n      lines: [entry],                       // <-- preferred for HTML rendering\n      texts: [entry.text],                  // <-- legacy compatibility\n    });\n  }\n}\n\n// ---- Pretty text (one line per turn; keeps the start ts for the turn) ----\nconst SENTENCE_SEP = '  ';\nconst transcript_text = transcript_merged\n  .map(m => `[${m.start_timestamp}] ${m.speaker}${m.email ? ` <${m.email}>` : ''}: ${m.lines.map(x => x.text).join(SENTENCE_SEP)}`)\n  .join('\\n');\n\n// ---- Output ----\nreturn {\n  json: {\n    meeting_title: toStr(b.meeting_title) || toStr(b.title) || null,\n    attendees,\n    scheduled_start_time: toStr(b.scheduled_start_time) || null,\n    scheduled_end_time: toStr(b.scheduled_end_time) || null,\n    recording_start_time: toStr(b.recording_start_time) || null,\n    recording_end_time: toStr(b.recording_end_time) || null,\n    recording_url: toStr(b.share_url) || toStr(b.url) || null,\n    transcript_merged,          // now includes lines[{ts,text}] + texts[]\n    transcript_text,            // still available if you need it\n    // summary_markdown: toStr(b?.default_summary?.markdown_formatted) || null, // optional\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a0653858-8a8c-4cc2-a5e1-c67a0a6d192d",
      "name": "필드 설정",
      "type": "n8n-nodes-base.set",
      "position": [
        432,
        -112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "180a6cda-50cc-4533-91a8-0736d1a9cd1f",
              "name": "output['Meeting Title']",
              "type": "string",
              "value": "={{ $json.output[\"Meeting Title\"] + \" - \" + $json.output[\"Scheduled Date/Time\"].replace(/:/g, \"-\") }}"
            },
            {
              "id": "957ef493-cd8b-4359-bb67-25206ac2a6f1",
              "name": "output['Recording URL']",
              "type": "string",
              "value": "={{ $json.output['Recording URL'] }}"
            },
            {
              "id": "38301e62-074a-4206-824a-64b37da4dd91",
              "name": "output['Scheduled Time']",
              "type": "string",
              "value": "={{ $json.output['Scheduled Date/Time'] }}"
            },
            {
              "id": "9b4fa931-0fce-4882-91b2-0af0523005ae",
              "name": "output['Recording Time']",
              "type": "string",
              "value": "={{ $json.output['Recording Date/Time'] }}"
            },
            {
              "id": "38605fc1-6c2d-4d4a-b2a5-597197dc3bd1",
              "name": "output.Attendees",
              "type": "string",
              "value": "={{ $json.output.Attendees }}"
            },
            {
              "id": "bd1f7948-0e94-4834-9066-bb92901a3867",
              "name": "output['Executive Summary']",
              "type": "string",
              "value": "={{ $json.output['Executive Summary'] }}"
            },
            {
              "id": "2757b576-8558-4089-9c6b-9782a42c32ca",
              "name": "output['Key Points']",
              "type": "string",
              "value": "={{ $json.output['Key Points'] }}"
            },
            {
              "id": "38b5bebe-b7ad-4e03-bbb9-64818b18cde6",
              "name": "output['Action Items']",
              "type": "string",
              "value": "={{ $json.output['Action Items'] }}"
            },
            {
              "id": "d07cb155-8799-4110-a244-f052471614e5",
              "name": "output.Decisions",
              "type": "string",
              "value": "={{ $json.output.Decisions }}"
            },
            {
              "id": "7a63e1ae-209e-4d6b-a042-3e59b7f1fe1d",
              "name": "output['Risks/Concerns']",
              "type": "string",
              "value": "={{ $json.output['Risks/Concerns'] }}"
            },
            {
              "id": "4ea08434-1d26-4a05-95d8-12bf2270a4a3",
              "name": "output['Open Questions']",
              "type": "string",
              "value": "={{ $json.output['Open Questions'] }}"
            },
            {
              "id": "20424379-7edc-4f43-b304-814dff3e713e",
              "name": "output['Entities — People']",
              "type": "string",
              "value": "={{ $json.output['Entities — People'] }}"
            },
            {
              "id": "d7eb163b-2de3-4dc7-bc8a-70b2bbef2ea1",
              "name": "output['Entities — Orgs/Places']",
              "type": "string",
              "value": "={{ $json.output['Entities — Orgs/Places'] }}"
            },
            {
              "id": "a8b13e21-867c-49c1-a920-237317be64d9",
              "name": "output['Entities — Numbers/Dates']",
              "type": "string",
              "value": "={{ $json.output['Entities — Numbers/Dates'] }}"
            },
            {
              "id": "e7438bd6-dec3-4162-a66a-fe7fa5e17056",
              "name": "transcript_merged",
              "type": "array",
              "value": "={{ $('Format Key Parts').item.json.transcript_merged }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "431be23c-a097-4cea-a77d-ee9e9a574ab5",
      "name": "HTML로 생성",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        -112
      ],
      "parameters": {
        "jsCode": "// --- helpers ---\nconst esc = (s='') => s.replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\nconst nl2li = s => s.split('\\n').map(x=>`<li>${esc(x.replace(/^-+\\s?/, ''))}</li>`).join('');\nconst chunk = (arr, n=3) => arr.reduce((a,_,i)=>(i%n?a[a.length-1].push(arr[i]):a.push([arr[i]]),a),[]);\n\n// --- inputs ---\nconst o = $json.output; // summary sections\nconst turns = ($node[\"Set Fields\"]?.json?.transcript_merged) || $json.transcript_merged || [];\n\n// --- transcript (speaker change + every 3 sentences; timestamp from first sentence in chunk) ---\nlet transcriptHtml = '—';\nif (Array.isArray(turns) && turns.length) {\n  transcriptHtml = turns.map(t => {\n    const parts = chunk((t.lines || []).filter(x => x && x.text), 3);\n    return parts.map(group => {\n      const stamp = group[0]?.ts || t.start_timestamp;\n      const text = group.map(x => (x.text || '').trim()).join(' ');\n      // email intentionally omitted\n      return `<strong>[${esc(stamp)}] ${esc(t.speaker)}:</strong> ${esc(text)}<br>`;\n    }).join('');\n  }).join('\\n');\n}\n\n// --- HTML (H2/H3/H4; compact, readable) ---\nconst html = `\n  <h2>${esc(o[\"Meeting Title\"])}</h2><br>\n\n  <h3>Scheduled Time</h3><p>${esc(o[\"Scheduled Time\"])}</p><br>\n  <h3>Recording Time</h3><p>${esc(o[\"Recording Time\"])}</p><br>\n  <h3>Recording URL</h3><p>${o[\"Recording URL\"] === \"None identified in the transcript\" ? \"—\" : `<a href=\"${esc(o[\"Recording URL\"])}\">${esc(o[\"Recording URL\"])}</a>`}</p><br>\n\n  <h3>Attendees</h3><ul>${nl2li(o[\"Attendees\"])}</ul><br>\n  <h3>Executive Summary</h3><p>${esc(o[\"Executive Summary\"])}</p><br>\n  <h3>Key Points</h3><ul>${nl2li(o[\"Key Points\"])}</ul><br>\n  <h3>Action Items</h3><ul>${nl2li(o[\"Action Items\"])}</ul><br>\n  <h3>Decisions</h3><ul>${nl2li(o[\"Decisions\"])}</ul><br>\n  <h3>Risks/Concerns</h3><ul>${nl2li(o[\"Risks/Concerns\"])}</ul><br>\n  <h3>Open Questions</h3><ul>${nl2li(o[\"Open Questions\"])}</ul><br>\n\n  <h3>Entities</h3>\n  <h4>People</h4><ul>${nl2li(o[\"Entities — People\"])}</ul>\n  <h4>Orgs/Places</h4><ul>${nl2li(o[\"Entities — Orgs/Places\"])}</ul>\n  <h4>Numbers/Dates</h4><ul>${nl2li(o[\"Entities — Numbers/Dates\"])}</ul><br>\n\n  <br><br><hr><br>\n  <h3>Full Transcript</h3>\n  ${transcriptHtml}\n`.trim();\n\n// -> Binary for Drive Upload (Convert to Google Doc = ON)\nconst filename = `${o[\"Meeting Title\"]} - Meeting Notes.html`;\nconst buf = Buffer.from(html, 'utf8');\nconst data = await this.helpers.prepareBinaryData(buf, filename, 'text/html');\nreturn [{ json: { filename }, binary: { data } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "236d20f7-0deb-48bd-b255-b7d72595958a",
      "name": "Google 문서로 변환",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1120,
        -112
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/drive/v3/files/{{ $json.id }}/copy",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n    \"name\": \"{{ $json.name.replace('.html', '') }}\",\n    \"mimeType\": \"application/vnd.google-apps.document\"\n  }",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "googleDriveOAuth2Api"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "IjoB5flCkLlcfjdH",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4c05b6b7-76ee-4747-8f5c-1fc8f679407b",
      "name": "HTML 파일 업로드",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        912,
        -112
      ],
      "parameters": {
        "name": "={{ $json.filename }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root",
          "cachedResultUrl": "https://drive.google.com/drive",
          "cachedResultName": "/ (Root folder)"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "IjoB5flCkLlcfjdH",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "0387aca5-1023-4606-9983-5e32801bf771",
      "name": "HTML 파일 삭제",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1344,
        16
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Upload File as HTML').item.json.id }}"
        },
        "options": {
          "deletePermanently": true
        },
        "operation": "deleteFile"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "IjoB5flCkLlcfjdH",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "744b6d13-5fb4-4da2-8087-1d20a44bf548",
      "name": "AI 회의 분석",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        96,
        -144
      ],
      "parameters": {
        "text": "=For the meeting transcript provided at the bottom, please follow the below template to write your reviewing comments about the meeting.\nNote that if there are sections below that you cannot identify in the transcript, then simply mention \"None identified in the transcript\"\n\n---\n\nMeeting:\nHeader line, nicely formatted and using the information exactly as provided.\nUse the official meeting title here (if provided): '{{ $json.meeting_title }}'\nOr, if there is no meeting title provided, create a title based on your analysis of the raw transcript provided at the end.\n\nRecording URL:\n{{ $json.recording_url }}\n\nScheduled Start Date/Time and End Time:\nFormatted as 'DD-MM-YYYY - HH:MM to HH:MM': {{ $json.scheduled_start_time }} - {{ $json.scheduled_end_time }} \n\nRecording Start Date/Time and End Time:\nFormatted as 'DD-MM-YYYY - HH:MM to HH:MM': {{ $json.recording_start_time }} - {{ $json.recording_end_time }}\n\n---\n\nAttendees:\n- names and email addresses, where available\nAttendee official list:{{ JSON.stringify($json.attendees) }}\nAlso check the transcript to identify additional speakers where possible\n\n\nExecutive Summary:\n- {1–5 sentences, no fluff. What’s the meeting about, key outcome(s), and immediate next step(s)?} \n\nKey Points:\n- {Concise point}.\n- {Another concise point}.\n(8 bullets max, but less is fine if more effective)\n\nAction Items:\n- List of each action item with any mentioned Owner, Due Date, Priority, and Source\n\nKey Decisions Made:\n- Decision:** {What was decided}. (By {who}, if stated) \n(6 bullets max, but less is fine if more effective)\n\n\nKey Risks / Concerns:\n- {Risk/issue}: {Context or impact in 1 line}\n(6 items maximum, but less is fine if more effective)\n\nOpen Questions:\n- {Question asked or unresolved point}\n- {Any missing info needed to proceed}\n(6 items maximum, but less is fine if more effective)\n\nEntities & References (quick extract):\n- People: {names}  \n- Organizations/Places: {entities}  \n- Numbers & Dates Mentioned (with brief context of why any numbers and dates are provided): {normalize money, dates, durations if present}\n\n\n**For Key Points, Action Items, Decisions, Risks/Concerns, Open Questions: return a single string. If items exist, output them as newline-separated lines (no bullets/numbers). If none, return the exact string “None identified in the transcript”.**\n\n---\n\n*** HERE IS THE TRANSCRIPT: ***\n\n{{ JSON.stringify($json.transcript_merged) }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "You are an expert meeting analyst. Produce a concise, executive-quality report from the transcript. Be precise, non-fluffy, and focus on important takeaways and outcomes from the meeting."
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "dc09aed6-a43b-470a-9463-72699cf38d4b",
      "name": "녹취록 존재 여부",
      "type": "n8n-nodes-base.if",
      "position": [
        -176,
        -96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "validation-check-1",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ JSON.stringify($json.transcript_merged) }}",
              "rightValue": ""
            },
            {
              "id": "validation-check-2",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.transcript_merged.length }}",
              "rightValue": 3
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a7e3e5c0-64be-462c-a86c-2325e783f693",
      "name": "스티키 노트",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        -272
      ],
      "parameters": {
        "width": 640,
        "height": 464,
        "content": "## Capture Meeting and Validate\nGet all Fathom meeting data, extract + format relevant contents, and validate there's transcript data before triggering the Gemini API request."
      },
      "typeVersion": 1
    },
    {
      "id": "ac4a64c4-6475-4b1e-a0bb-548a253350a0",
      "name": "스티키 노트1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 576,
        "height": 464,
        "content": "## Perform Meeting Analysis (AI)\nConduct full analysis of the meeting transcript and associated data, and create the meeting notes (summary, actions, etc.)."
      },
      "typeVersion": 1
    },
    {
      "id": "9a58821a-8ff7-46cd-81e9-4407b69f04d3",
      "name": "스티키 노트2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -272
      ],
      "parameters": {
        "color": 3,
        "width": 944,
        "height": 464,
        "content": "## Convert to HTML Output and Upload to Google Drive\nGet all the meeting data + the newly generated meeting notes (from Gemini), create in HTML format (with correct MIME type for HTML rendering), create as Google Doc, and delete HTML file."
      },
      "typeVersion": 1
    },
    {
      "id": "bc8793a2-268d-4379-81de-057fe26b0ce7",
      "name": "페이지 레이아웃 개선",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1344,
        -160
      ],
      "parameters": {
        "url": "=https://docs.googleapis.com/v1/documents/{{$node[\"Convert to Google Doc\"].json.id}}:batchUpdate",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"requests\": [\n    {\n      \"updateDocumentStyle\": {\n        \"documentStyle\": {\n          \"marginTop\":    {\"magnitude\": 36, \"unit\": \"PT\"},\n          \"marginBottom\": {\"magnitude\": 36, \"unit\": \"PT\"},\n          \"marginLeft\":   {\"magnitude\": 36, \"unit\": \"PT\"},\n          \"marginRight\":  {\"magnitude\": 36, \"unit\": \"PT\"}\n        },\n        \"fields\": \"marginTop,marginBottom,marginLeft,marginRight\"\n      }\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "googleDriveOAuth2Api"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "IjoB5flCkLlcfjdH",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "76eeb8a0-d87a-442c-8cc9-924b7ee28b59",
      "name": "스티키 노트3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        -736
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 1168,
        "content": "# **Fathom Meeting Summary & Actions AI Agent**\n  *Transcript → Analysis → Formatted Doc, automatically*\n\nThis workflow automatically converts Fathom meeting transcripts into beautifully formatted Google Docs with AI-generated summaries, key points, decisions, and action items.\n\n  **Good to know**\n  - Works fully with Fathom free account\n  - Webhook responds immediately to prevent Fathom timeout and duplicate triggers\n  - Validates transcript quality (3+ conversation turns) before AI processing to save costs\n  - Uses Google Gemini API (generous free tier and rate limits, typically enough to avoid paying for API requests, but check latest pricing at [Google AI Pricing](https://ai.google.dev/pricing))\n  - Creates temporary HTML file that's auto-deleted after conversion\n\n  ## Who's it for\n  Individuals or teams using Fathom for meetings who want more control and flexibility with their AI meeting analysis and storage independently of Fathom, as well as automatic, formatted documentation without manual note-taking. Perfect for recurring syncs, client meetings, or interview debriefs.\n\n  ## How it works\n  1. Fathom webhook triggers when meeting ends and sends transcript data\n  2. Validates transcript has meaningful conversation (3+ turns)\n  3. Google Gemini AI analyzes transcript and generates structured summary (key points, decisions, actions, next steps)\n  4. Creates formatted HTML with proper styling\n  5. Uploads to Google Drive and converts to native Google Doc\n  6. Reduces page margins for readability and deletes temporary HTML file\n\n  ## Requirements\n  - Fathom account with API webhook access (available on free tier)\n  - Google Drive account (OAuth2)\n  - Google Docs account (OAuth2)\n  - Google Gemini API key ([Get free key here](https://makersuite.google.com/app/apikey))\n\n  ## How to set up\n  1. Add credentials: Google Drive OAuth2, Google Docs OAuth2, Google Gemini API\n  2. Copy the webhook URL from the Get Fathom Meeting webhook node (Test URL first, change to Production URL when ready)\n  3. In Fathom: Settings → API Access → Add → Add webhook URL and select all events including \"Transcript\"\n  4. Test with a short meeting, verify Google Doc appears in Drive\n  5. Activate workflow\n\n  ## Customizing this workflow\n  - **Change save location**: Edit \"Upload File as HTML\" node → update \"Parent Folder\"\n  - **Modify AI output**: Edit \"AI Meeting Analysis\" node → customize the prompt to add/remove sections (e.g., risks, follow-ups, sentiment, etc)\n  - **Adjust document margins**: Edit \"Reduce Page Margins\" node → change margin pixel values\n  - **Add notifications**: E.g. add Slack/Email node after \"Convert to Google Doc\" to notify team when summary is ready"
      },
      "typeVersion": 1
    },
    {
      "id": "ca149d7b-3a90-4882-bc9d-2dbddf83872c",
      "name": "Fathom 회의 가져오기",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -560,
        -96
      ],
      "webhookId": "2fab6c8f-ade4-49ba-b160-7cf6aa11cb15",
      "parameters": {
        "path": "2fab6c8f-ade4-49ba-b160-7cf6aa11cb15",
        "options": {
          "rawBody": true,
          "binaryPropertyName": "data"
        },
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "705471d5-b542-49f6-9342-660884907ea4",
      "name": "스티키 노트7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 192,
        "content": "  ## **Quick Troubleshooting**\n  * **\"Transcript Present?\" fails**: Fathom must send `transcript_merged` with 3+ conversation turns (i.e. only send to Gemini for analysis if there's a genuine transcript)\n  * **HTML as plain text**: Check \"Convert to Google Doc\" uses POST to `/copy` endpoint\n  * **401/403 errors**: Re-authorize Google credentials\n  * **Inadequate meeting notes**: Edit prompts in \"AI Meeting Analysis\" node"
      },
      "typeVersion": 1
    },
    {
      "id": "7fd24ad7-f811-42ee-9ca9-fb8490cdf8eb",
      "name": "스티키 노트4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        -640
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 336,
        "content": "## Sample File and Storage Output\n- [Google Doc meeting notes - sample](https://docs.google.com/document/d/1tCC90dIpgb8NtuOJ_jSTCPn4MxORB-XcvwdeljYzC9w/edit?usp=drive_link)\n- Google Drive sample folder output:\n![](https://i.postimg.cc/MH8Vtny4/Sample-Google-Drive-Output.png)"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "NrO9p0iGYkywEkTP",
    "executionOrder": "v1"
  },
  "versionId": "f8839c0b-a795-4745-bca5-dab6a00a5977",
  "connections": {
    "a0653858-8a8c-4cc2-a5e1-c67a0a6d192d": {
      "main": [
        [
          {
            "node": "431be23c-a097-4cea-a77d-ee9e9a574ab5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "431be23c-a097-4cea-a77d-ee9e9a574ab5": {
      "main": [
        [
          {
            "node": "4c05b6b7-76ee-4747-8f5c-1fc8f679407b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a4e520d6-c9bd-4060-94b4-e61f7ef38b19": {
      "main": [
        [
          {
            "node": "dc09aed6-a43b-470a-9463-72699cf38d4b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ca149d7b-3a90-4882-bc9d-2dbddf83872c": {
      "main": [
        [
          {
            "node": "a4e520d6-c9bd-4060-94b4-e61f7ef38b19",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "744b6d13-5fb4-4da2-8087-1d20a44bf548": {
      "main": [
        [
          {
            "node": "a0653858-8a8c-4cc2-a5e1-c67a0a6d192d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "dc09aed6-a43b-470a-9463-72699cf38d4b": {
      "main": [
        [
          {
            "node": "744b6d13-5fb4-4da2-8087-1d20a44bf548",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4c05b6b7-76ee-4747-8f5c-1fc8f679407b": {
      "main": [
        [
          {
            "node": "236d20f7-0deb-48bd-b255-b7d72595958a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "236d20f7-0deb-48bd-b255-b7d72595958a": {
      "main": [
        [
          {
            "node": "0387aca5-1023-4606-9983-5e32801bf771",
            "type": "main",
            "index": 0
          },
          {
            "node": "bc8793a2-268d-4379-81de-057fe26b0ce7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a362776f-d572-4983-9dc6-a161ddd30392": {
      "ai_languageModel": [
        [
          {
            "node": "744b6d13-5fb4-4da2-8087-1d20a44bf548",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "73dc3249-bc5d-4676-95e8-bcd4b2fea8e9": {
      "ai_outputParser": [
        [
          {
            "node": "744b6d13-5fb4-4da2-8087-1d20a44bf548",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}
자주 묻는 질문

이 워크플로우를 어떻게 사용하나요?

위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.

이 워크플로우는 어떤 시나리오에 적합한가요?

고급

유료인가요?

이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.

워크플로우 정보
난이도
고급
노드 수18
카테고리-
노드 유형10
난이도 설명

고급 사용자를 위한 16+개 노드의 복잡한 워크플로우

저자
Dean Pike

Dean Pike

@deanjp

Versatile problem-solver building scalable, AI-driven systems that replace manual, error-prone operations for high-growth companies. Specializing in operational strategy, project delivery, and practical AI and workflow automation solutions that free leaders to focus on strategic priorities while increasing margins without additional hires.

외부 링크
n8n.io에서 보기

이 워크플로우 공유

카테고리

카테고리: 34