{
  "id": "c03eb8b4-ee66-55",
  "name": "Invoice clients from tagged Google Calendar events with Google Docs, Gmail approval and a billing log",
  "nodes": [
    {
      "parameters": {
        "content": "## Invoice clients from tagged Google Calendar events with Google Docs, Gmail approval and a billing log\n\n### How it works\n\nPut the client's tag in square brackets in a calendar event title, for example `Design review [acme]`, and at the start of each month the workflow turns last month's tagged time into invoices. You approve each one, and no meeting is ever billed twice.\n\n1. **On the 1st of each month, last month's events are read** from your Google Calendar, with recurring meetings expanded into individual events.\n2. **Tagged time is priced in code.** Each event's length is rounded up to your billing increment (15 minutes by default) and multiplied by that client's hourly rate from a Clients sheet. Tax is added and all money is computed in integer cents.\n3. **Only real billable time counts.** Events you declined, cancelled events and all-day events are skipped. Tags that don't match a client are listed in your approval email instead of being guessed.\n4. **Nothing is billed twice.** Every billed event's id is written to a billing log, and events already in the log are skipped, so re-running the month is safe.\n5. **You approve every invoice in Gmail** before anything reaches the client. The approval email shows each line, the hours and the total. Reject it and nothing is sent.\n6. **Approved invoices are made from your Google Docs template**, exported as PDF, emailed to the client and recorded in an invoice log, and their events are added to the billing log.\n\n### Setup steps\n\nTakes ≈15 minutes.\n\n1. Create a Google Sheet with three tabs:\n   - `Clients` with columns `Tag, Client, Email, Hourly rate, Currency, Tax rate %`\n   - `Invoices` with columns `Invoice, Client, Period, Hours, Total, Currency, Sent at, Document`\n   - `Billing log` with columns `Event id, Date, Client, Invoice, Hours, Billed at`\n2. Select those tabs in **Read Your Clients and Rates**, **Log the Invoice**, **Read the Billing Log** and **Add the Events to the Billing Log**.\n3. Select your calendar in **Read Last Month's Events** and connect your Google Calendar credential.\n4. Create a Google Docs invoice template with the placeholders `{{number}}`, `{{client}}`, `{{period}}`, `{{lines}}`, `{{hours}}`, `{{rate}}`, `{{subtotal}}`, `{{tax}}`, `{{total}}`, `{{currency}}` and `{{issued}}`, and select it in **Copy the Invoice Template**.\n5. Connect Google Drive, Google Docs and Gmail, and put your own address in **Ask You to Approve the Invoice**.\n6. Tag a few test events with `[yourtag]`, add yourself as a client with that tag, run the workflow manually and approve the email to see the whole path.\n\n### Customization\n\n- **Billing rules:** `ROUND_TO_MINUTES`, `MINIMUM_MINUTES` and the invoice-number `PREFIX` are at the top of **Collect the Billable Time**.\n- **Different period:** change **Set the Billing Period** to bill weekly or for a fixed date range.\n- **Several calendars:** duplicate **Read Last Month's Events** for each calendar and merge them before the pricing step.\n- **Gapless numbering:** if your country requires an unbroken invoice sequence, replace the month-based number with a gapless number series.\n\n*Your calendar is already your timesheet. Tag the event, approve the invoice, and the billing log keeps it honest.*",
        "height": 1940,
        "width": 520
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1180,
        -260
      ],
      "id": "e7c98dc7-3d02-58bf-b7b2-4b8ee2163085",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Read last month's calendar and your rates\n\nOn the 1st, the previous month is set as the billing period. Your clients and rates and the billing log are read first, then every event in the period.",
        "height": 440,
        "width": 1240,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -580,
        -260
      ],
      "id": "47ba69d1-0dcd-5de3-9b21-649433b76422",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## Price the tagged time in code\n\nTagged events are rounded, priced at the client's rate and grouped into one draft invoice per client. Declined, cancelled, all-day and already-billed events are skipped.",
        "height": 440,
        "width": 280,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        716,
        -260
      ],
      "id": "02a4d7a1-833f-575d-a884-783a802d36e5",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## You approve every invoice\n\nEach draft is emailed to you with every line and the total. Nothing reaches a client until you approve it; a rejected draft is simply dropped.",
        "height": 640,
        "width": 760,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1052,
        -260
      ],
      "id": "09c54d67-0440-5902-a462-b59246df5692",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Send the PDF and record what was billed\n\nYour Docs template is filled in, exported as PDF and sent. The invoice is logged, and every event on it goes into the billing log so it can never be billed again.",
        "height": 440,
        "width": 1720,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1868,
        -260
      ],
      "id": "093755f5-324a-58aa-a04c-792bbb853561",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtDayOfMonth": 1,
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "e985a6d6-9c6d-50e8-b0f1-2575ac0757cb",
      "name": "On the 1st of Every Month",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -498,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "jsCode": "// Bill the previous calendar month by default (the workflow runs on the 1st).\n// For a custom range, replace these two lines with fixed dates.\nconst now = new Date();\nconst from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1));\nconst to = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));\nreturn [{ json: { from: from.toISOString(), to: to.toISOString(), label: from.toISOString().slice(0, 7) } }];"
      },
      "id": "b86910bb-33c2-5a77-b2d5-9e090c7d79e2",
      "name": "Set the Billing Period",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -258,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "4a18706f-62f8-54ee-86e2-f65217b5b926",
      "name": "Read Your Clients and Rates",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -18,
        -34.69999999999999
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "1e236475-38a2-5896-998e-658492315c45",
      "name": "Read the Billing Log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        222,
        -34.69999999999999
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "returnAll": true,
        "timeMin": "={{ $('Set the Billing Period').first().json.from }}",
        "timeMax": "={{ $('Set the Billing Period').first().json.to }}",
        "options": {
          "singleEvents": true
        }
      },
      "id": "1cebcf89-4e63-50df-b7b9-647867a1f2b3",
      "name": "Read Last Month's Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        462,
        -34.69999999999999
      ],
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "// Turns tagged calendar events into one draft invoice per client.\n// A tag is the client's Tag in square brackets anywhere in the event title,\n// e.g. \"Design review [acme]\". Matching is case-insensitive.\n\n// ---- Your billing rules ----\nconst ROUND_TO_MINUTES = 15;   // each event is rounded UP to this\nconst MINIMUM_MINUTES = 15;    // shortest billable event\nconst PREFIX = 'INV-';         // invoice number = PREFIX + month + '-' + client tag\n// -----------------------------\n\nconst period = $('Set the Billing Period').first().json;\nconst clientRows = $('Read Your Clients and Rates').all().map(i => i.json);\nconst billed = new Set($('Read the Billing Log').all().map(i => String(i.json['Event id'] ?? '').trim()).filter(Boolean));\nconst events = $input.all().map(i => i.json).filter(e => e && e.id);\n\nconst clients = {};\nfor (const c of clientRows) {\n  const tag = String(c.Tag ?? '').trim().toLowerCase();\n  if (tag) clients[tag] = c;\n}\nconst cents = v => Math.round(Number(String(v ?? '').replace(/[^0-9.\\-]/g, '')) * 100);\nconst money = c => (c / 100).toFixed(2);\n\nconst unknownTags = new Set();\nconst skipped = { declined: 0, allDay: 0, cancelled: 0, alreadyBilled: 0, untagged: 0 };\nconst byClient = {};\nconst seen = new Set();\n\nfor (const e of events) {\n  if (seen.has(e.id)) continue; seen.add(e.id);\n  if (e.status === 'cancelled') { skipped.cancelled++; continue; }\n  if (!e.start?.dateTime || !e.end?.dateTime) { skipped.allDay++; continue; }\n  const me = (e.attendees ?? []).find(a => a.self);\n  if (me && me.responseStatus === 'declined') { skipped.declined++; continue; }\n  const tags = [...String(e.summary ?? '').matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1].trim().toLowerCase());\n  const tag = tags.find(t => clients[t]);\n  if (!tag) { if (tags.length) tags.forEach(t => unknownTags.add(t)); else skipped.untagged++; continue; }\n  if (billed.has(e.id)) { skipped.alreadyBilled++; continue; }\n\n  const raw = (new Date(e.end.dateTime) - new Date(e.start.dateTime)) / 60000;\n  if (!(raw > 0)) continue;\n  const minutes = Math.max(MINIMUM_MINUTES, Math.ceil(raw / ROUND_TO_MINUTES) * ROUND_TO_MINUTES);\n  (byClient[tag] ??= []).push({\n    eventId: e.id,\n    date: e.start.dateTime.slice(0, 10),\n    title: String(e.summary).replace(/\\s*\\[[^\\]]+\\]\\s*/g, ' ').trim() || 'Session',\n    minutes,\n  });\n}\n\nconst out = [];\nfor (const [tag, lines] of Object.entries(byClient)) {\n  const c = clients[tag];\n  lines.sort((a, b) => a.date.localeCompare(b.date));\n  const rateCents = cents(c['Hourly rate']);\n  const taxRate = Number(String(c['Tax rate %'] ?? 0).replace('%', '')) || 0;\n  const priced = lines.map(l => ({ ...l, hours: (l.minutes / 60).toFixed(2), amountCents: Math.round(rateCents * l.minutes / 60) }));\n  const subtotal = priced.reduce((a, l) => a + l.amountCents, 0);\n  const tax = Math.round(subtotal * taxRate / 100);\n  const hours = priced.reduce((a, l) => a + l.minutes, 0) / 60;\n  const currency = String(c.Currency ?? 'USD').trim().toUpperCase();\n  out.push({ json: {\n    number: PREFIX + period.label + '-' + tag.toUpperCase().replace(/[^A-Z0-9]+/g, ''),\n    period: period.label,\n    tag, client: String(c.Client ?? tag).trim(), email: String(c.Email ?? '').trim(),\n    currency, rate: money(rateCents), taxRate,\n    hours: hours.toFixed(2), subtotal: money(subtotal), tax: money(tax), total: money(subtotal + tax),\n    lines: priced.map(l => ({ eventId: l.eventId, date: l.date, title: l.title, hours: l.hours, amount: money(l.amountCents) })),\n    linesText: priced.map(l => l.date + '  ' + l.title + '  ' + l.hours + ' h  ' + money(l.amountCents) + ' ' + currency).join('\\n'),\n    notes: (unknownTags.size ? 'Unknown tags this period (not billed): ' + [...unknownTags].join(', ') + '. ' : '')\n      + (skipped.alreadyBilled ? skipped.alreadyBilled + ' event(s) were already billed and skipped.' : ''),\n  } });\n}\nreturn out;"
      },
      "id": "4bf0eac5-2180-5e1a-a57e-d5acb4bae4f0",
      "name": "Collect the Billable Time",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        798,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "sendTo": "you@example.com",
        "subject": "=Approve invoice {{ $json.number }}: {{ $json.client }}, {{ $json.total }} {{ $json.currency }}",
        "message": "=Draft invoice {{ $json.number }} for {{ $json.client }} ({{ $json.email }}), period {{ $json.period }}.\n\n{{ $json.linesText }}\n\n{{ $json.hours }} h at {{ $json.rate }} {{ $json.currency }}/h\nSubtotal: {{ $json.subtotal }}\nTax ({{ $json.taxRate }}%): {{ $json.tax }}\nTotal: {{ $json.total }} {{ $json.currency }}\n\n{{ $json.notes }}\n\nApprove to send it to the client. Reject to drop this draft; its events stay unbilled.",
        "responseType": "approval",
        "approvalOptions": {
          "values": {
            "approvalType": "double"
          }
        },
        "options": {}
      },
      "id": "0eb6f016-9022-5f08-a971-afd2280292f6",
      "name": "Ask You to Approve the Invoice",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1134,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-approved",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "id": "5e220736-bad7-5817-9e62-efff506865f3",
      "name": "Did You Approve It?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1374,
        -34.69999999999999
      ]
    },
    {
      "parameters": {},
      "id": "391adc4b-e07a-5e9f-b24f-b2771b5947b8",
      "name": "Drop the Rejected Draft",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1614,
        165.3
      ]
    },
    {
      "parameters": {
        "operation": "copy",
        "fileId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "name": "={{ $('Collect the Billable Time').item.json.number }} {{ $('Collect the Billable Time').item.json.client }}",
        "sameFolder": true,
        "options": {}
      },
      "id": "7271f976-51bc-54c3-981c-dca98bfadb13",
      "name": "Copy the Invoice Template",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1950,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentURL": "={{ $json.id }}",
        "actionsUi": {
          "actionFields": [
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{number}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.number }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{client}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.client }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{period}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.period }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{lines}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.linesText }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{hours}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.hours }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{rate}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.rate }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{subtotal}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.subtotal }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{tax}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.tax }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{total}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.total }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{currency}}",
              "matchCase": true,
              "replaceText": "={{ $('Collect the Billable Time').item.json.currency }}"
            },
            {
              "object": "text",
              "action": "replaceAll",
              "text": "{{issued}}",
              "matchCase": true,
              "replaceText": "={{ $now.toISODate() }}"
            }
          ]
        }
      },
      "id": "e2bc921a-96c6-538f-97b8-e5c3179b74ff",
      "name": "Fill In the Invoice",
      "type": "n8n-nodes-base.googleDocs",
      "typeVersion": 2,
      "position": [
        2190,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Copy the Invoice Template').item.json.id }}"
        },
        "options": {
          "fileName": "={{ $('Collect the Billable Time').item.json.number }}.pdf",
          "googleFileConversion": {
            "conversion": {
              "docsToFormat": "application/pdf"
            }
          }
        }
      },
      "id": "3dcabf6d-d622-5fe3-96b5-a50afb06b599",
      "name": "Export the Invoice as PDF",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        2430,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Collect the Billable Time').item.json.email }}",
        "subject": "=Invoice {{ $('Collect the Billable Time').item.json.number }} for {{ $('Collect the Billable Time').item.json.period }}",
        "emailType": "text",
        "message": "=Hello {{ $('Collect the Billable Time').item.json.client }},\n\nPlease find attached invoice {{ $('Collect the Billable Time').item.json.number }} for {{ $('Collect the Billable Time').item.json.hours }} hours in {{ $('Collect the Billable Time').item.json.period }}, totalling {{ $('Collect the Billable Time').item.json.total }} {{ $('Collect the Billable Time').item.json.currency }}.\n\nThank you.",
        "options": {
          "appendAttribution": false,
          "attachmentsUi": {
            "attachmentsBinary": [
              {
                "property": "data"
              }
            ]
          }
        }
      },
      "id": "e8362d13-348c-5d28-b5d2-2ee80833197b",
      "name": "Email the Invoice to the Client",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2670,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Invoice": "={{ $('Collect the Billable Time').item.json.number }}",
            "Client": "={{ $('Collect the Billable Time').item.json.client }}",
            "Period": "={{ $('Collect the Billable Time').item.json.period }}",
            "Hours": "={{ $('Collect the Billable Time').item.json.hours }}",
            "Total": "={{ $('Collect the Billable Time').item.json.total }}",
            "Currency": "={{ $('Collect the Billable Time').item.json.currency }}",
            "Sent at": "={{ $now.toISO() }}",
            "Document": "=https://docs.google.com/document/d/{{ $('Copy the Invoice Template').item.json.id }}"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "ea65c574-9a4c-50f9-9dd2-c2b682bc8301",
      "name": "Log the Invoice",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2910,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "jsCode": "// One billing-log row per event on each sent invoice.\nconst out = [];\n$input.all().forEach((item, i) => {\n  const inv = $('Collect the Billable Time').itemMatching(i).json;\n  for (const l of inv.lines) {\n    out.push({ json: {\n      'Event id': l.eventId, Date: l.date, Client: inv.client, Invoice: inv.number,\n      Hours: l.hours, 'Billed at': new Date().toISOString(),\n    }, pairedItem: { item: i } });\n  }\n});\nreturn out;"
      },
      "id": "dc3a7550-95f3-5814-b55e-384370275cdd",
      "name": "List the Billed Events",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3150,
        -34.69999999999999
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "960ff09e-cc57-54ea-acb0-18cc5f351bb7",
      "name": "Add the Events to the Billing Log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        3390,
        -34.69999999999999
      ]
    }
  ],
  "connections": {
    "On the 1st of Every Month": {
      "main": [
        [
          {
            "node": "Set the Billing Period",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set the Billing Period": {
      "main": [
        [
          {
            "node": "Read Your Clients and Rates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Your Clients and Rates": {
      "main": [
        [
          {
            "node": "Read the Billing Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the Billing Log": {
      "main": [
        [
          {
            "node": "Read Last Month's Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Last Month's Events": {
      "main": [
        [
          {
            "node": "Collect the Billable Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect the Billable Time": {
      "main": [
        [
          {
            "node": "Ask You to Approve the Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask You to Approve the Invoice": {
      "main": [
        [
          {
            "node": "Did You Approve It?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did You Approve It?": {
      "main": [
        [
          {
            "node": "Copy the Invoice Template",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Drop the Rejected Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Copy the Invoice Template": {
      "main": [
        [
          {
            "node": "Fill In the Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fill In the Invoice": {
      "main": [
        [
          {
            "node": "Export the Invoice as PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export the Invoice as PDF": {
      "main": [
        [
          {
            "node": "Email the Invoice to the Client",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email the Invoice to the Client": {
      "main": [
        [
          {
            "node": "Log the Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log the Invoice": {
      "main": [
        [
          {
            "node": "List the Billed Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List the Billed Events": {
      "main": [
        [
          {
            "node": "Add the Events to the Billing Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}
