{
  "name": "Lumeor | Webinar follow-up (Zoom + HubSpot + Gmail)",
  "nodes": [
    {
      "parameters": {
        "content": "## Webinar follow-up\nCall the webhook when a Zoom webinar ends. The workflow waits, polls Zoom until the recording is ready, pulls registrants, the attendance report and Q&A, and labels everyone attended, left early or no-show. Claude drafts a three-line recap and answers to questions left unanswered live. **The presenter approves the AI content in Slack** before any email uses it. Each person is updated in HubSpot and gets a segment-specific email from Gmail.\n\n**Webhook body (JSON):** `{ \"webinar_id\": \"84512345678\", \"webinar_topic\": \"Automating month-end close\", \"deck_notes\": \"paste slide text or talking points\", \"duration_minutes\": 60 }`\n\n**Setup**\n1. Zoom: create a General (OAuth) app with scopes to read webinars, registrants, reports and recordings, and connect it as a Zoom OAuth2 credential. Reports need a paid Zoom plan with reporting.\n2. HubSpot: create contact properties last_webinar_name (text), last_webinar_status (text) and last_webinar_minutes (number).\n3. Credentials: Zoom OAuth2, Anthropic, Slack, HubSpot Service Key, Gmail OAuth2.\n4. Replace REPLACE_WITH_APPROVAL_CHANNEL, REPLACE_WITH_ALERTS_CHANNEL, REPLACE_WITH_SENDER_NAME and REPLACE_WITH_COMPANY_POSTAL_ADDRESS.\n\nGmail has daily sending limits. For more than a few hundred registrants, send through your marketing platform instead.\n\nModel: claude-opus-5-5. For simple, high-volume classification steps, claude-haiku-5-5 is a cheaper option.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/automate-webinar-follow-up/",
        "height": 900,
        "width": 600,
        "color": 7
      },
      "id": "636172bb-9a5f-4509-a680-df24c84a5d1a",
      "name": "About this template",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -480
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lumeor-webinar-ended",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "b528ace9-bc3e-4414-bbcf-595b3834740e",
      "name": "Webinar ended",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "411362ba-3583-493c-90b9-81767c392dce"
    },
    {
      "parameters": {
        "resume": "timeInterval",
        "amount": 30,
        "unit": "minutes"
      },
      "id": "85253c5b-16ba-4599-b5e1-8d909db73c2d",
      "name": "Wait for Zoom reports",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        220,
        300
      ],
      "webhookId": "65c544dd-b23a-4ab9-acab-9d68d0809b85"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.zoom.us/v2/meetings/{{ $('Webinar ended').first().json.body.webinar_id }}/recordings",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "id": "b6b12d81-f3b7-401b-99b3-2ea0d92720d3",
      "name": "Check recording",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "3fa1706d-3b7b-4300-b71b-1547e047e364",
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 200,
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "ready"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "5d84ba6d-13ed-4d0f-ae74-712e3bb248c2",
                    "leftValue": "={{ $runIndex }}",
                    "rightValue": 8,
                    "operator": {
                      "type": "number",
                      "operation": "lt"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "retry"
            }
          ]
        },
        "looseTypeValidation": true,
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "give up"
        }
      },
      "id": "874d3902-e463-4e56-b9f4-7a882354f005",
      "name": "Recording ready?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.zoom.us/v2/webinars/{{ $('Webinar ended').first().json.body.webinar_id }}/registrants",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "page_size",
              "value": "300"
            }
          ]
        },
        "options": {
          "pagination": {
            "pagination": {
              "paginationMode": "updateAParameterInEachRequest",
              "parameters": {
                "parameters": [
                  {
                    "type": "qs",
                    "name": "next_page_token",
                    "value": "={{ $response.body.next_page_token }}"
                  }
                ]
              },
              "paginationCompleteWhen": "other",
              "completeExpression": "={{ !$response.body.next_page_token }}",
              "limitPagesFetched": true,
              "maxRequests": 20
            }
          }
        }
      },
      "id": "9d3cd138-d79f-43d8-99ff-2cf647d5fb15",
      "name": "Get registrants",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        200
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.zoom.us/v2/report/webinars/{{ $('Webinar ended').first().json.body.webinar_id }}/participants",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "page_size",
              "value": "300"
            }
          ]
        },
        "options": {
          "pagination": {
            "pagination": {
              "paginationMode": "updateAParameterInEachRequest",
              "parameters": {
                "parameters": [
                  {
                    "type": "qs",
                    "name": "next_page_token",
                    "value": "={{ $response.body.next_page_token }}"
                  }
                ]
              },
              "paginationCompleteWhen": "other",
              "completeExpression": "={{ !$response.body.next_page_token }}",
              "limitPagesFetched": true,
              "maxRequests": 20
            }
          }
        }
      },
      "id": "28521d79-0861-4a14-91b5-0dfd336e575d",
      "name": "Get attendance report",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        200
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.zoom.us/v2/report/webinars/{{ $('Webinar ended').first().json.body.webinar_id }}/qa",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "options": {}
      },
      "id": "611d667c-c171-47f1-99a0-9ffa3072c6b0",
      "name": "Get Q&A report",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        200
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Labels everyone attended, left early or no-show, and collects unanswered questions.\nconst EARLY_LEAVE_SHARE = 0.5; // under half the session counts as left early\nconst input = $('Webinar ended').first().json.body;\nconst recording = $('Check recording').first().json.body || {};\nconst durationMin = Number(input.duration_minutes) || Number(recording.duration) || 60;\n\nconst people = new Map();\nconst key = (e) => String(e || '').trim().toLowerCase();\nfor (const page of $('Get registrants').all()) {\n  for (const r of page.json.registrants || []) {\n    if (!key(r.email)) continue;\n    people.set(key(r.email), { email: key(r.email), first_name: r.first_name || '', last_name: r.last_name || '', minutes: 0, first_join: null, questions: [] });\n  }\n}\nfor (const page of $('Get attendance report').all()) {\n  for (const p of page.json.participants || []) {\n    const k = key(p.user_email);\n    if (!k) continue;\n    if (!people.has(k)) {\n      const [first = '', ...rest] = String(p.name || '').split(' ');\n      people.set(k, { email: k, first_name: first, last_name: rest.join(' '), minutes: 0, first_join: null, questions: [] });\n    }\n    const person = people.get(k);\n    person.minutes += Math.round((Number(p.duration) || 0) / 60); // rejoins are summed\n    if (p.join_time && (!person.first_join || p.join_time < person.first_join)) person.first_join = p.join_time;\n  }\n}\nconst unanswered = [];\nfor (const q of $('Get Q&A report').first().json.questions || []) {\n  for (const d of q.question_details || []) {\n    const person = people.get(key(q.email));\n    if (person) person.questions.push(d.question);\n    if (!String(d.answer || '').trim()) unanswered.push({ question_id: unanswered.length + 1, email: key(q.email), name: q.name || '', question: d.question });\n  }\n}\nconst list = [...people.values()].map((p) => ({\n  ...p,\n  segment: p.minutes === 0 ? 'no_show' : p.minutes < durationMin * EARLY_LEAVE_SHARE ? 'left_early' : 'attended',\n}));\nreturn [{\n  json: {\n    webinar_topic: input.webinar_topic || recording.topic || 'our webinar',\n    deck_notes: String(input.deck_notes || '').slice(0, 12000),\n    duration_minutes: durationMin,\n    recording_url: recording.share_url || '',\n    recording_passcode: recording.password || '',\n    counts: { attended: list.filter((p) => p.segment === 'attended').length, left_early: list.filter((p) => p.segment === 'left_early').length, no_show: list.filter((p) => p.segment === 'no_show').length },\n    people: list,\n    unanswered: unanswered.slice(0, 25),\n  },\n}];"
      },
      "id": "2a55f7c6-048d-4302-8858-4bc2edd3d037",
      "name": "Segment registrants",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'claude-opus-5-5', max_tokens: 3000, system: \"You help a presenter follow up after a B2B webinar.\\n1. recap_lines: exactly 3 short lines summarizing what the session covered, based only on the deck notes and topic.\\n2. answers: for each unanswered question, a 2 to 4 sentence answer based only on the deck notes. If the notes do not cover it, say the team will follow up personally and set defer_to_sales to true. Always defer pricing, contract and roadmap questions to sales.\\nNever invent features, prices, customers, dates or statistics.\", output_config: { \"format\": { \"type\": \"json_schema\", \"schema\": { \"type\": \"object\", \"properties\": { \"recap_lines\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }, \"answers\": { \"type\": \"array\", \"items\": { \"type\": \"object\", \"properties\": { \"question_id\": { \"type\": \"integer\" }, \"answer\": { \"type\": \"string\" }, \"defer_to_sales\": { \"type\": \"boolean\" } }, \"required\": [ \"question_id\", \"answer\", \"defer_to_sales\" ], \"additionalProperties\": false } } }, \"required\": [ \"recap_lines\", \"answers\" ], \"additionalProperties\": false } } }, messages: [ { role: 'user', content: 'Webinar: ' + $json.webinar_topic + '\\n\\nDeck notes:\\n' + ($json.deck_notes || '(none provided)') + '\\n\\nUnanswered questions (JSON):\\n' + JSON.stringify($json.unanswered.map(q => ({ question_id: q.question_id, question: q.question }))) } ] }) }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "c0987b66-f225-47f2-8100-e82a56054737",
      "name": "Draft recap and answers",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        200
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "REPLACE_WITH_APPROVAL_CHANNEL",
          "mode": "name"
        },
        "message": "=:movie_camera: *Webinar follow-up ready: {{ $('Segment registrants').first().json.webinar_topic }}*\nAttended {{ $('Segment registrants').first().json.counts.attended }}, left early {{ $('Segment registrants').first().json.counts.left_early }}, no-show {{ $('Segment registrants').first().json.counts.no_show }}.\n\n{{ (() => { const t = ($json.content || []).find(b => b.type === 'text'); if (!t) return '_No AI draft available. Approving sends emails without a recap or answers._'; try { const d = JSON.parse(t.text); const qs = $('Segment registrants').first().json.unanswered; return '*Recap*\\n' + d.recap_lines.map(l => '- ' + l).join('\\n') + '\\n\\n*Answers*\\n' + (d.answers.map(a => '*Q:* ' + ((qs.find(q => q.question_id === a.question_id) || {}).question || '?') + '\\n*A:* ' + a.answer).join('\\n\\n') || 'No unanswered questions.'); } catch (e) { return '_AI draft could not be read. Approving sends emails without it._'; } })() }}\n\nApprove to include this in the emails. Reject to send the emails with the recording only.",
        "responseType": "approval",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "Approve and send",
            "disapproveLabel": "Send without AI text"
          }
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "limitType": "afterTimeInterval",
              "resumeAmount": 12,
              "resumeUnit": "hours"
            }
          },
          "appendAttribution": false
        }
      },
      "id": "42eb374d-58dd-470b-bd91-22e02fb3361f",
      "name": "Presenter approval in Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1980,
        200
      ],
      "webhookId": "412f35d8-3e2b-47ed-a0ec-7da39f9ac67f"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// One email per person. AI recap and answers are included only if the presenter approved them.\nconst SENDER = 'REPLACE_WITH_SENDER_NAME';\nconst ADDRESS = 'REPLACE_WITH_COMPANY_POSTAL_ADDRESS';\nconst w = $('Segment registrants').first().json;\nconst res = $('Draft recap and answers').first().json;\nconst decision = $input.first().json;\nconst approved = !!(decision.data && decision.data.approved === true);\nconst out = [];\nlet recap = [];\nlet answers = {};\nif (approved) {\n  // Opus 5.5 may return a thinking block first, so find the text block explicitly.\n  const block = (res.content || []).find((b) => b.type === 'text');\n  if (res.stop_reason === 'refusal' || res.stop_reason === 'max_tokens') {\n    out.push({ json: { error: `Claude stopped with stop_reason=${res.stop_reason}; emails sent without AI text`, details: { webinar: w.webinar_topic } } });\n  } else if (!block) {\n    out.push({ json: { error: 'Claude response had no text block; emails sent without AI text', details: { webinar: w.webinar_topic } } });\n  } else {\n    try {\n      const d = JSON.parse(block.text);\n      recap = (d.recap_lines || []).slice(0, 3);\n      for (const a of d.answers || []) answers[a.question_id] = a;\n    } catch (e) {\n      out.push({ json: { error: 'Claude returned invalid JSON; emails sent without AI text', details: { webinar: w.webinar_topic } } });\n    }\n  }\n}\nconst esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst link = w.recording_url ? `<p><a href=\"${esc(w.recording_url)}\">Watch the recording</a>${w.recording_passcode ? ` (passcode ${esc(w.recording_passcode)})` : ''}</p>` : '';\nconst recapHtml = recap.length ? `<p>In short:</p><ul>${recap.map((l) => `<li>${esc(l)}</li>`).join('')}</ul>` : '';\nfor (const p of w.people) {\n  const hi = p.first_name ? `Hi ${esc(p.first_name)},` : 'Hi,';\n  const mine = w.unanswered.filter((q) => q.email === p.email && answers[q.question_id]);\n  const qaHtml = mine.map((q) => `<p><strong>You asked:</strong> ${esc(q.question)}<br>${esc(answers[q.question_id].answer)}</p>`).join('');\n  let subject;\n  let bodyHtml;\n  if (p.segment === 'attended') {\n    subject = `Thanks for joining: ${w.webinar_topic}`;\n    bodyHtml = `<p>${hi}</p><p>Thanks for attending ${esc(w.webinar_topic)}. Here is the recording to share with your team.</p>${link}${qaHtml}`;\n  } else if (p.segment === 'left_early') {\n    subject = `The part you missed: ${w.webinar_topic}`;\n    bodyHtml = `<p>${hi}</p><p>Thanks for stopping by ${esc(w.webinar_topic)}. You were with us for about ${p.minutes} minutes, so the recording picks up the rest.</p>${recapHtml}${link}${qaHtml}`;\n  } else {\n    subject = `Recording: ${w.webinar_topic}`;\n    bodyHtml = `<p>${hi}</p><p>Sorry we missed you at ${esc(w.webinar_topic)}. The recording is ready whenever you are.</p>${recapHtml}${link}`;\n  }\n  bodyHtml += `<p>${esc(SENDER)}</p><p style=\"font-size:12px;color:#888\">${esc(ADDRESS)}<br>You registered for this webinar. Reply \"unsubscribe\" and we will not email you about future events.</p>`;\n  out.push({ json: { email: p.email, first_name: p.first_name, last_name: p.last_name, segment: p.segment, minutes: p.minutes, subject, html: bodyHtml } });\n}\nreturn out;"
      },
      "id": "d6a44c10-c01f-4098-9e03-e002ef82449f",
      "name": "Build segment emails",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        200
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "authentication": "appToken",
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.email }}",
        "additionalFields": {
          "firstName": "={{ $json.first_name }}",
          "lastName": "={{ $json.last_name }}",
          "customPropertiesUi": {
            "customPropertiesValues": [
              {
                "property": "last_webinar_name",
                "value": "={{ $('Segment registrants').first().json.webinar_topic }}"
              },
              {
                "property": "last_webinar_status",
                "value": "={{ $json.segment }}"
              },
              {
                "property": "last_webinar_minutes",
                "value": "={{ $json.minutes }}"
              }
            ]
          }
        },
        "options": {
          "resolveData": false
        }
      },
      "id": "3888a88d-7a0f-48f0-b42d-01268069a0b7",
      "name": "Update contact in HubSpot",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2.1,
      "position": [
        2420,
        180
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "send",
        "sendTo": "={{ $('Build segment emails').item.json.email }}",
        "subject": "={{ $('Build segment emails').item.json.subject }}",
        "emailType": "html",
        "message": "={{ $('Build segment emails').item.json.html }}",
        "options": {
          "appendAttribution": false,
          "senderName": "REPLACE_WITH_SENDER_NAME"
        }
      },
      "id": "d5b7475b-e04e-4345-8131-e57141d7b67e",
      "name": "Send follow-up email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2640,
        180
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "REPLACE_WITH_ALERTS_CHANNEL",
          "mode": "name"
        },
        "text": "=:warning: *Webinar follow-up needs attention* (webinar {{ $('Webinar ended').first().json.body.webinar_id }})\n*Reason:* {{ $json.error ? (typeof $json.error === 'string' ? $json.error : $json.error.message) : 'Zoom recording still not available after 8 checks (last status ' + $json.statusCode + '). Run the workflow again later.' }}\n*Details:* {{ JSON.stringify($json.details || {}).slice(0, 500) }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "44cd3bad-1bc6-4867-ac14-a77b3cc85966",
      "name": "Alert: webinar follow-up needs attention",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1100,
        520
      ]
    }
  ],
  "connections": {
    "Webinar ended": {
      "main": [
        [
          {
            "node": "Wait for Zoom reports",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Zoom reports": {
      "main": [
        [
          {
            "node": "Check recording",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check recording": {
      "main": [
        [
          {
            "node": "Recording ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Recording ready?": {
      "main": [
        [
          {
            "node": "Get registrants",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait for Zoom reports",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert: webinar follow-up needs attention",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get registrants": {
      "main": [
        [
          {
            "node": "Get attendance report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get attendance report": {
      "main": [
        [
          {
            "node": "Get Q&A report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Q&A report": {
      "main": [
        [
          {
            "node": "Segment registrants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Segment registrants": {
      "main": [
        [
          {
            "node": "Draft recap and answers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft recap and answers": {
      "main": [
        [
          {
            "node": "Presenter approval in Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Presenter approval in Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Presenter approval in Slack": {
      "main": [
        [
          {
            "node": "Build segment emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build segment emails": {
      "main": [
        [
          {
            "node": "Update contact in HubSpot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert: webinar follow-up needs attention",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update contact in HubSpot": {
      "main": [
        [
          {
            "node": "Send follow-up email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
