{
  "id": "2b61422142e3609c",
  "name": "10 - Review Request (Drafts Only)",
  "nodes": [
    {
      "parameters": {
        "content": "## WHAT THIS LOOP CLOSES\n\n**The leak: happy customers never get asked for the review.**\n\nThe job went well, the invoice got paid, everyone moved on. The customer would have left five stars, but nobody asked, and a week later the moment is gone. Meanwhile the one unhappy customer finds the review page all by themselves.\n\nThis loop fires the moment a job is marked done or an invoice is paid. The AI writes a personal review ask that references the actual job, with your Google review link, as a Gmail draft plus a copy-ready SMS. If no review lands within 7 days, it drafts one polite reminder. Then it stops asking.",
        "height": 440,
        "width": 420,
        "color": 4
      },
      "id": "aa10c1e2-4b91-4d27-9c44-0e6a2b7f1a10",
      "name": "WHAT THIS LOOP CLOSES",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -560
      ]
    },
    {
      "parameters": {
        "content": "## 60-SECOND SETUP\n\n1. **Webhook node:** copy the production URL and call it from your job tracker or invoicing tool (or a Make/Zapier webhook) when a job is marked done or an invoice is paid. Send customer_name, customer_email, customer_phone, job_description, invoice_number.\n2. **Settings (Edit Me) node:** the one place you edit. Fill `airtableBaseId`, `airtableTableId`, `businessContext` (paste your business brain), and `googleReviewLink` (Google Business Profile under Ask for reviews). The ask path and the reminder path both read these four values from here.\n3. **AI nodes (2):** attach your OpenAI credential.\n4. **Gmail nodes (2):** attach your Gmail OAuth credential.\n5. **Airtable nodes (3):** attach your credential. The base and table are already wired to the Settings node. Your Review Requests table needs these fields: Customer Name, Customer Email, Customer Phone, Job Summary, Invoice Number, Ask Date, Status, Review Received (checkbox), Reminder Date.\n6. **Slack nodes (2):** attach your Slack credential and set the channel (or set the env var `YOUR_SLACK_CHANNEL`).\n7. When a review lands on Google, tick **Review Received** on the record. That kills the reminder. Activate the workflow.",
        "height": 520,
        "width": 480,
        "color": 5
      },
      "id": "bb20d2f3-5ca2-4e38-8d55-1f7b3c8a2b20",
      "name": "60-SECOND SETUP",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        400,
        -560
      ]
    },
    {
      "parameters": {
        "content": "## SAFETY: DRAFTS NOT SENDS\n\nThe AI writes the ask. It never sends it.\n\nThe email lands in your Gmail Drafts folder. The SMS arrives in Slack as text you copy into your own phone. The 7-day reminder is also a draft. Every message a customer sees was sent by a human who read it first.\n\nReview asks also have a compliance edge: Google prohibits incentivized reviews and review gating. The prompts forbid both, and the human send is your second line of defense.",
        "height": 360,
        "width": 400,
        "color": 3
      },
      "id": "cc30e3a4-6db3-4f49-9e66-2a8c4d9b3c30",
      "name": "SAFETY: DRAFTS NOT SENDS",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        940,
        -480
      ]
    },
    {
      "parameters": {
        "content": "## SETTINGS (EDIT ME) — ONE PLACE\n\nFill these four values ONCE in the **Settings (Edit Me)** node and every other node reads from here. Nothing else needs the base id, the table id, the business brain, or the review link.\n\n- **airtableBaseId** — your Airtable base id (starts with `app...`).\n- **airtableTableId** — the Review Requests table id (starts with `tbl...`).\n- **businessContext** — your business brain (about us, voice, offers).\n- **googleReviewLink** — your Google review link.\n\nThe ask path and the reminder path both read this one node, so the brain cannot drift between them.",
        "height": 360,
        "width": 400,
        "color": 6
      },
      "id": "de51f5c6-8fd5-4b6b-ba88-4caf6fbd5e51",
      "name": "SETTINGS NOTE",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a0a1a2a3-b4b5-4c60-8172-839405a6b701",
              "name": "airtableBaseId",
              "value": "YOUR_AIRTABLE_BASE_ID",
              "type": "string"
            },
            {
              "id": "b1b2b3b4-c5c6-4d71-9283-94a5b6c7d802",
              "name": "airtableTableId",
              "value": "YOUR_REVIEW_REQUESTS_TABLE_ID",
              "type": "string"
            },
            {
              "id": "c2c3c4c5-d6d7-4e82-a394-a5b6c7d8e903",
              "name": "businessContext",
              "value": "PASTE YOUR BUSINESS BRAIN CONTEXT HERE. Include: who you are and what you do (01-about-us), your tone of voice and how you sign messages (02-voice-and-style), and the kind of work you want highlighted in reviews (03-offers-and-faqs). The AI uses this block to write review asks that sound like you, not like a robot. Both the ask path and the reminder path read this one value.",
              "type": "string"
            },
            {
              "id": "d3d4d5d6-e7e8-4f93-b4a5-b6c7d8e9f004",
              "name": "googleReviewLink",
              "value": "YOUR_GOOGLE_REVIEW_LINK (looks like https://g.page/r/YOUR_PLACE_CODE/review, get it from Google Business Profile under Ask for reviews)",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "ef62a6d7-9ae6-4c7c-cb99-5dba7ace6f62",
      "name": "Settings (Edit Me)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -300,
        180
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "fa01b2c3-d4e5-4f60-8172-839405a6b711",
              "leftValue": "={{ $json.headers }}",
              "rightValue": "",
              "operator": {
                "type": "object",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0b73c8e9-abf7-4d8d-dcaa-6ecb8bdf701b",
      "name": "Ask or Reminder?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -80,
        180
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "review-request",
        "options": {},
        "authentication": "headerAuth"
      },
      "id": "dd40f4b5-7ec4-4a5a-af77-3b9d5eac4d40",
      "name": "Job Done / Invoice Paid",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Accept the webhook payload from your job tracker or invoicing tool.\n// Works whether fields arrive at the top level or nested under body.\nconst out = [];\nfor (const item of $input.all()) {\n  const src = item.json.body || item.json;\n  out.push({\n    json: {\n      customerName: src.customer_name || src.customerName || src.name || 'there',\n      customerEmail: src.customer_email || src.customerEmail || src.email || '',\n      customerPhone: src.customer_phone || src.customerPhone || src.phone || '',\n      jobSummary: src.job_description || src.jobSummary || src.job || src.description || 'the recent job',\n      jobDate: src.job_date || src.jobDate || src.completed_at || new Date().toISOString().slice(0, 10),\n      invoiceNumber: src.invoice_number || src.invoiceNumber || src.invoice || '',\n    },\n  });\n}\nreturn out;"
      },
      "id": "ee50a5c6-8fd5-4b6b-ba88-4caf6fbd5e50",
      "name": "Normalize Job Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "11a1b2c3-d4e5-4f60-8172-839405a6b7c8",
              "name": "businessContext",
              "value": "={{ $('Settings (Edit Me)').item.json.businessContext }}",
              "type": "string"
            },
            {
              "id": "22b2c3d4-e5f6-4a71-9283-94a5b6c7d8e9",
              "name": "googleReviewLink",
              "value": "={{ $('Settings (Edit Me)').item.json.googleReviewLink }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "ff60b6d7-9ae6-4c7c-cb99-5dba7ace6f60",
      "name": "Business Brain Context",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "gpt-4o"
        },
        "operation": "message",
        "messages": {
          "values": [
            {
              "content": "=You write review requests for completed jobs. You DRAFT only. You never send anything and you never claim a message was sent. A human will review and send both drafts.\n\n=== CONTEXT (the business brain, from the Settings (Edit Me) node) ===\n{{ $('Settings (Edit Me)').item.json.businessContext }}\n=== END CONTEXT ===\n\n=== THE JOB ===\nCustomer: {{ $json.customerName }}\nWhat we did: {{ $json.jobSummary }}\nCompleted: {{ $json.jobDate }}\nInvoice: {{ $json.invoiceNumber }}\n=== END JOB ===\n\nGoogle review link to include in both drafts: {{ $('Settings (Edit Me)').item.json.googleReviewLink }}\n\nRules:\n- Write in the voice and style defined in the CONTEXT block. Match its language.\n- Reference the actual job in the first line. No generic 'we value your feedback'.\n- Ask for the review once, plainly. One link, one ask.\n- Never offer discounts, gifts, or anything in exchange for the review. Never ask for a 5-star review, ask for an honest one.\n- Email under 100 words. SMS under 300 characters, same ask, tighter.\n- Do not invent facts that are not in the CONTEXT block or the job details.\n- Sign off exactly the way the CONTEXT block says to sign off.\n\nReturn EXACTLY this format and nothing else:\nSubject: <subject line>\nEMAIL:\n<email body>\nSMS:\n<sms text>"
            }
          ]
        },
        "options": {}
      },
      "id": "0a70c7e8-abf7-4d8d-dcaa-6ecb8bdf700a",
      "name": "Write Review Ask (AI)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Split the AI output into subject, email body, and SMS text,\n// and carry the job fields forward.\nconst out = [];\nfor (const item of $input.all()) {\n  const raw =\n    item.json.message?.content ??\n    item.json.choices?.[0]?.message?.content ??\n    item.json.text ??\n    '';\n  const text = String(raw).trim();\n\n  let subject = 'How did we do?';\n  const subjectMatch = text.match(/^Subject:\\s*(.+)$/m);\n  if (subjectMatch) subject = subjectMatch[1].trim();\n\n  const emailMatch = text.match(/EMAIL:\\s*([\\s\\S]*?)(?:\\nSMS:|$)/);\n  const smsMatch = text.match(/SMS:\\s*([\\s\\S]*)$/);\n\n  const emailBody = emailMatch ? emailMatch[1].trim() : text;\n  const smsBody = smsMatch ? smsMatch[1].trim() : '';\n\n  const job = $('Normalize Job Payload').itemMatching(out.length)?.json || {};\n  out.push({ json: { ...job, subject, emailBody, smsBody } });\n}\nreturn out;"
      },
      "id": "1b80d8f9-bca8-4e9e-edbb-7fdc9cea811b",
      "name": "Parse Ask Drafts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.emailBody }}",
        "options": {
          "sendTo": "={{ $json.customerEmail }}"
        }
      },
      "id": "2c90e9aa-cdb9-4faf-fecc-80ed0adfb22c",
      "name": "Create Email Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1100,
        0
      ]
    },
    {
      "parameters": {
        "operation": "create",
        "base": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableBaseId }}",
          "mode": "id"
        },
        "table": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableTableId }}",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Customer Name": "={{ $('Parse Ask Drafts').item.json.customerName }}",
            "Customer Email": "={{ $('Parse Ask Drafts').item.json.customerEmail }}",
            "Customer Phone": "={{ $('Parse Ask Drafts').item.json.customerPhone }}",
            "Job Summary": "={{ $('Parse Ask Drafts').item.json.jobSummary }}",
            "Invoice Number": "={{ $('Parse Ask Drafts').item.json.invoiceNumber }}",
            "Ask Date": "={{ $now.toISO() }}",
            "Status": "Asked"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "3da0fabb-deca-4aba-afdd-91fe1beac33d",
      "name": "Log Review Request (CRM)",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2.1,
      "position": [
        1320,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{ $env.YOUR_SLACK_CHANNEL }}",
          "mode": "id"
        },
        "text": "=Review ask drafted for {{ $('Parse Ask Drafts').item.json.customerName }}:\n• Job: {{ $('Parse Ask Drafts').item.json.jobSummary }}\n• Email draft is in Gmail Drafts (subject: {{ $('Parse Ask Drafts').item.json.subject }})\n• SMS text, copy it into your phone and send:\n```{{ $('Parse Ask Drafts').item.json.smsBody }}```\n\nReview both, press send, done. If no review lands in 7 days, the reminder draft writes itself.",
        "otherOptions": {}
      },
      "id": "4eb10bcc-efdb-4bcb-b0ee-a20f2cfbd44e",
      "name": "Ping Owner: Ask Ready",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1540,
        0
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "5fc21cdd-f0ec-4dcc-c1ff-b3a03dace55f",
      "name": "Daily 9am Reminder Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        360
      ]
    },
    {
      "parameters": {
        "operation": "search",
        "base": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableBaseId }}",
          "mode": "id"
        },
        "table": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableTableId }}",
          "mode": "id"
        },
        "filterByFormula": "AND({Status} = 'Asked', NOT({Review Received}), DATETIME_DIFF(NOW(), {Ask Date}, 'days') >= 7)",
        "options": {}
      },
      "id": "60d32dee-01fd-4edd-d2aa-c4b14ebfd660",
      "name": "Find Unreviewed Asks (CRM)",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2.1,
      "position": [
        220,
        360
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "33c3d4e5-f6a7-4b82-a394-a5b6c7d8e9f0",
              "name": "businessContext",
              "value": "={{ $('Settings (Edit Me)').item.json.businessContext }}",
              "type": "string"
            },
            {
              "id": "44d4e5f6-a7b8-4c93-b4a5-b6c7d8e9f001",
              "name": "googleReviewLink",
              "value": "={{ $('Settings (Edit Me)').item.json.googleReviewLink }}",
              "type": "string"
            },
            {
              "id": "55e5f6a7-b8c9-4da4-c5b6-c7d8e9f00112",
              "name": "recordId",
              "value": "={{ $json.id }}",
              "type": "string"
            },
            {
              "id": "66f6a7b8-c9da-4eb5-d6c7-d8e9f0011223",
              "name": "customerName",
              "value": "={{ $json['Customer Name'] }}",
              "type": "string"
            },
            {
              "id": "77a7b8c9-daeb-4fc6-e7d8-e9f001122334",
              "name": "customerEmail",
              "value": "={{ $json['Customer Email'] }}",
              "type": "string"
            },
            {
              "id": "88b8c9da-ebfc-40d7-f8e9-f00112233445",
              "name": "jobSummary",
              "value": "={{ $json['Job Summary'] }}",
              "type": "string"
            },
            {
              "id": "99c9daeb-fc0d-41e8-a9f0-011223344556",
              "name": "askDate",
              "value": "={{ $json['Ask Date'] }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": false,
        "options": {}
      },
      "id": "71e43eff-120e-4fee-e3bb-d5c25fc0e771",
      "name": "Prep Reminder + Context",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        360
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "gpt-4o"
        },
        "operation": "message",
        "messages": {
          "values": [
            {
              "content": "=You write one polite reminder for a review request that got no response. You DRAFT only. You never send anything and you never claim a message was sent. A human will review and send this draft.\n\n=== CONTEXT (the business brain, from the Settings (Edit Me) node) ===\n{{ $('Settings (Edit Me)').item.json.businessContext }}\n=== END CONTEXT ===\n\n=== THE SITUATION ===\nCustomer: {{ $json.customerName }}\nThe job: {{ $json.jobSummary }}\nWe asked for a review on {{ $json.askDate }}. Seven days have passed and no review has appeared.\n=== END SITUATION ===\n\nGoogle review link to include: {{ $('Settings (Edit Me)').item.json.googleReviewLink }}\n\nRules:\n- This is the one and only reminder. Keep it light. Never use phrases like 'last chance' or 'final reminder'.\n- Shorter than the first ask. Under 70 words.\n- No guilt, no pressure, no 'just following up' opener. Reference the job, restate the link, and give an easy out, something like 'if now is not the time, no worries at all'.\n- Never offer anything in exchange for the review. Ask for an honest review, not a 5-star one.\n- Do not invent facts that are not in the CONTEXT block or the situation details.\n- Sign off exactly the way the CONTEXT block says to sign off.\n\nReturn EXACTLY this format and nothing else:\nSubject: <subject line>\n\n<email body>"
            }
          ]
        },
        "options": {}
      },
      "id": "82f54f00-231f-4a0f-f4cc-e6d36a0df882",
      "name": "Write Polite Reminder (AI)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        660,
        360
      ]
    },
    {
      "parameters": {
        "jsCode": "// Split the AI output into subject + body and carry the record fields forward.\nconst out = [];\nfor (const item of $input.all()) {\n  const raw =\n    item.json.message?.content ??\n    item.json.choices?.[0]?.message?.content ??\n    item.json.text ??\n    '';\n  const text = String(raw).trim();\n  const lines = text.split('\\n');\n  let subject = 'One small favour';\n  let bodyStart = 0;\n  if (lines[0] && lines[0].toLowerCase().startsWith('subject:')) {\n    subject = lines[0].slice(8).trim();\n    bodyStart = 1;\n  }\n  const body = lines.slice(bodyStart).join('\\n').trim();\n  const record = $('Prep Reminder + Context').itemMatching(out.length)?.json || {};\n  out.push({ json: { ...record, subject, body } });\n}\nreturn out;"
      },
      "id": "9306500a-3420-4b10-a5dd-f7e47b1e0993",
      "name": "Parse Reminder Draft",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        360
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.body }}",
        "options": {
          "sendTo": "={{ $json.customerEmail }}"
        }
      },
      "id": "a417610b-4531-4c21-b6ee-08f58c2f1aa4",
      "name": "Create Reminder Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1100,
        360
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "base": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableBaseId }}",
          "mode": "id"
        },
        "table": {
          "__rl": true,
          "value": "={{ $('Settings (Edit Me)').item.json.airtableTableId }}",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "id": "={{ $('Parse Reminder Draft').item.json.recordId }}",
            "Status": "Reminded",
            "Reminder Date": "={{ $now.toISO() }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "b528720c-5642-4d32-c7ff-19a69d302bb5",
      "name": "Mark Reminder Drafted (CRM)",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2.1,
      "position": [
        1320,
        360
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{ $env.YOUR_SLACK_CHANNEL }}",
          "mode": "id"
        },
        "text": "=Polite review reminder drafted for {{ $('Parse Reminder Draft').item.json.customerName }}:\n• Job: {{ $('Parse Reminder Draft').item.json.jobSummary }}\n• Asked on {{ $('Parse Reminder Draft').item.json.askDate }}, 7+ days, no review detected\n• Subject: {{ $('Parse Reminder Draft').item.json.subject }}\n\nThe draft is in Gmail Drafts. This is the one and only reminder. Review it and press send.",
        "otherOptions": {}
      },
      "id": "c639830d-6753-4e43-d800-2ab7ae413cc6",
      "name": "Ping Owner: Reminder Ready",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1540,
        360
      ]
    },
    {
      "parameters": {
        "content": "## WEBHOOK AUTH (do not skip)\n\nThis workflow's trigger is a public URL. It ships with **Header Auth** switched on so a stranger who guesses the URL cannot fire your loop, spend your API credits, or send on your behalf.\n\nSetup, once:\n1. In the webhook node, open the Credential field and create a new **Header Auth** credential.\n2. Name: `X-Kratt-Token`. Value: any long random string you invent (30+ characters).\n3. In whatever system calls this webhook, add that same header and value to the request.\n\nIf your caller genuinely cannot send custom headers, switch Authentication back to None, but then treat the URL itself as a secret and never paste it anywhere public.",
        "height": 360,
        "width": 400
      },
      "id": "auth-note-10",
      "name": "Sticky: Webhook Auth",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1020,
        -560
      ]
    }
  ],
  "connections": {
    "Job Done / Invoice Paid": {
      "main": [
        [
          {
            "node": "Settings (Edit Me)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (Edit Me)": {
      "main": [
        [
          {
            "node": "Ask or Reminder?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask or Reminder?": {
      "main": [
        [
          {
            "node": "Normalize Job Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Find Unreviewed Asks (CRM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Job Payload": {
      "main": [
        [
          {
            "node": "Business Brain Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Business Brain Context": {
      "main": [
        [
          {
            "node": "Write Review Ask (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Review Ask (AI)": {
      "main": [
        [
          {
            "node": "Parse Ask Drafts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Ask Drafts": {
      "main": [
        [
          {
            "node": "Create Email Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Email Draft": {
      "main": [
        [
          {
            "node": "Log Review Request (CRM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Review Request (CRM)": {
      "main": [
        [
          {
            "node": "Ping Owner: Ask Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily 9am Reminder Check": {
      "main": [
        [
          {
            "node": "Settings (Edit Me)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Unreviewed Asks (CRM)": {
      "main": [
        [
          {
            "node": "Prep Reminder + Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Reminder + Context": {
      "main": [
        [
          {
            "node": "Write Polite Reminder (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Polite Reminder (AI)": {
      "main": [
        [
          {
            "node": "Parse Reminder Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Reminder Draft": {
      "main": [
        [
          {
            "node": "Create Reminder Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Reminder Draft": {
      "main": [
        [
          {
            "node": "Mark Reminder Drafted (CRM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Reminder Drafted (CRM)": {
      "main": [
        [
          {
            "node": "Ping Owner: Reminder Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}