{
  "name": "Turn quote request mails into freee quotation drafts with Claude, priced from your sheet",
  "nodes": [
    {
      "id": "c7000000-0000-4000-8000-000000000001",
      "name": "New request mail",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "subject:(quote OR quotation OR 見積) -subject:\"[n8n quote]\""
        },
        "options": {}
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000002",
      "name": "Read the mail",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "parameters": {
        "jsCode": "// Sender, subject and the plain text of the mail.\nconst m = $json;\nconst from = (m.from && m.from.value && m.from.value[0]) || {};\nconst text = String(m.text || m.textAsHtml || m.snippet || '').replace(/\\r/g, '').trim();\nreturn [{ json: { source: 'gmail', from_email: String(from.address || '').toLowerCase(), from_name: from.name || '', subject: m.subject || '', text } }];"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000003",
      "name": "Paste a request",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.6,
      "position": [
        0,
        300
      ],
      "parameters": {
        "formTitle": "Make a freee quotation from a request",
        "formDescription": "Paste a quotation request (for example the body of a mail). A quotation is created in freee as unsent; nobody receives anything until you send it from freee.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Customer e-mail",
              "fieldName": "Email",
              "fieldType": "email",
              "requiredField": false,
              "placeholder": "buyer@example.com"
            },
            {
              "fieldLabel": "Request",
              "fieldName": "Request",
              "fieldType": "textarea",
              "requiredField": true,
              "placeholder": "Please send a quote for 500 brochures (A4) and 2 staff for the day.\n\nExample Trading Co., Ltd. / Sato"
            }
          ]
        },
        "responseMode": "lastNode",
        "options": {
          "path": "request-to-freee-quote"
        }
      },
      "webhookId": "3c8e1f52-7a4d-4b96-8e21-5d0f9b7c6a13"
    },
    {
      "id": "c7000000-0000-4000-8000-000000000004",
      "name": "Read the form",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ],
      "parameters": {
        "jsCode": "return [{ json: { source: 'form', from_email: String($json.Email || '').trim().toLowerCase(), from_name: '', subject: 'Request from the form', text: String($json.Request || '').replace(/\\r/g, '').trim() } }];"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000005",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        150
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": true,
        "assignments": {
          "assignments": [
            {
              "id": "c01",
              "name": "freee_company_id",
              "value": "PASTE_YOUR_FREEE_COMPANY_ID",
              "type": "string"
            },
            {
              "id": "c02",
              "name": "price_sheet_id",
              "value": "PASTE_YOUR_SPREADSHEET_ID",
              "type": "string"
            },
            {
              "id": "c03",
              "name": "price_sheet_tab",
              "value": "Price list",
              "type": "string"
            },
            {
              "id": "c04",
              "name": "staff_email",
              "value": "staff@example.com",
              "type": "string"
            },
            {
              "id": "c05",
              "name": "default_partner_title",
              "value": "御中",
              "type": "string"
            },
            {
              "id": "c06",
              "name": "tax_entry_method",
              "value": "out",
              "type": "string"
            },
            {
              "id": "c07",
              "name": "tax_fraction",
              "value": "omit",
              "type": "string"
            },
            {
              "id": "c08",
              "name": "expiration_days",
              "value": 30,
              "type": "number"
            },
            {
              "id": "c09",
              "name": "timezone",
              "value": "Asia/Tokyo",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000006",
      "name": "Read the price list",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        150
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.price_sheet_id }}/values/{{ encodeURIComponent($json.price_sheet_tab) }}!A1:F500",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000007",
      "name": "Read freee partners",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        150
      ],
      "parameters": {
        "url": "=https://api.freee.co.jp/api/1/partners?company_id={{ $('Config').first().json.freee_company_id }}&limit=3000",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000008",
      "name": "Prepare the prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        150
      ],
      "parameters": {
        "jsCode": "// The price list becomes a catalog for Claude (codes, names, other names, units; no prices).\nconst c = $('Config').first().json;\nconst sheet = $('Read the price list').first().json;\nconst freee = $json;\nif (sheet.statusCode !== 200) return [{ json: { ok: false, problem: 'Could not read the price list sheet (HTTP ' + sheet.statusCode + '). Check price_sheet_id and price_sheet_tab in Config.' } }];\nif (freee.statusCode !== 200) return [{ json: { ok: false, problem: 'Could not read the partners in freee (HTTP ' + freee.statusCode + '). Check the freee credential, the app permissions and freee_company_id.' } }];\nconst rows = (sheet.body.values || []);\nconst head = (rows[0] || []).map((h) => String(h).trim().toLowerCase());\nconst col = (k) => head.indexOf(k);\nfor (const k of ['code', 'name', 'unit', 'unit_price', 'tax_rate']) if (col(k) < 0) return [{ json: { ok: false, problem: 'The price list needs a header row with: code, name, aliases, unit, unit_price, tax_rate (missing: ' + k + ').' } }];\nconst catalog = rows.slice(1).filter((r) => r[col('code')]).map((r) => ({\n  code: String(r[col('code')]).trim(), name: String(r[col('name')] || '').trim(),\n  aliases: col('aliases') >= 0 ? String(r[col('aliases')] || '').trim() : '',\n  unit: String(r[col('unit')] || '').trim(), unit_price: String(r[col('unit_price')] ?? '').replace(/[,\\s]/g, ''),\n  tax_rate: Number(String(r[col('tax_rate')] ?? '').replace('%', '')),\n}));\nif (!catalog.length) return [{ json: { ok: false, problem: 'The price list is empty.' } }];\nif (!c.text) return [{ json: { ok: false, problem: 'The request is empty.' } }];\nconst list = catalog.map((x) => x.code + ' | ' + x.name + (x.aliases ? ' | also: ' + x.aliases : '') + ' | unit: ' + x.unit).join('\\n');\nreturn [{ json: { ok: true, catalog, partners: freee.body.partners || [], list, text: c.text.slice(0, 8000) } }];"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000009",
      "name": "Ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1320,
        150
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000010",
      "name": "Claude Haiku 4.5",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [
        1540,
        370
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-haiku-4-5-20251001",
          "cachedResultName": "Claude Haiku 4.5"
        },
        "options": {}
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000011",
      "name": "Read the items",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        1540,
        150
      ],
      "parameters": {
        "promptType": "define",
        "text": "={{ 'Catalog (code | name | other names | unit):\\n' + $json.list + '\\n\\nRequest:\\n' + $json.text }}",
        "messages": {
          "messageValues": [
            {
              "type": "SystemMessagePromptTemplate",
              "message": "You read a customer's request for a quotation and list what they ask for. You never set or mention prices.\nFor each requested item:\n- \"code\": the catalog code only if the request clearly means that catalog item; otherwise null. Do not pick the nearest item when it is a different thing.\n- \"requested\": the item as the customer wrote it.\n- \"quantity\": the number the customer wrote for it, or null if no number is written. Do not guess. \"一式\" or \"a set\" counts as 1.\n- \"unit\": the unit as written (部, 枚, sets, people...), or null.\n- \"quote\": the exact words from the request, copied character by character, that contain the item and its quantity.\nAlso return \"customer_company\": the customer's company name as written in the request (often in the signature), or null; and \"subject\": a short title for the quotation in the language of the request (topic only, no prices).\nReturn only JSON: {\"customer_company\": \"...\", \"subject\": \"...\", \"items\": [{\"code\": \"...\", \"requested\": \"...\", \"quantity\": 1, \"unit\": \"...\", \"quote\": \"...\"}]}"
            }
          ]
        }
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000012",
      "name": "Check and price",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        150
      ],
      "parameters": {
        "jsCode": "// Code decides what goes into the quotation. Prices come only from the price list.\nconst p = $('Prepare the prompt').first().json;\nconst c = $('Config').first().json;\nconst norm = (s) => String(s ?? '').normalize('NFKC').toLowerCase().replace(/[\\s,、。「」\"'“”]/g, '');\nconst numIn = (text, q) => {\n  const t = String(text).normalize('NFKC').replace(/,/g, '');\n  return new RegExp('(^|[^\\\\d.])' + String(q).replace('.', '\\\\.') + '([^\\\\d.]|$)').test(t);\n};\nlet a;\ntry { a = JSON.parse(String($json.text ?? '').match(/\\{[\\s\\S]*\\}/)[0]); }\ncatch (e) { return [{ json: { ok: false, problem: 'Could not read the AI answer as JSON. Try again.' } }]; }\nconst byCode = new Map(p.catalog.map((x) => [x.code, x]));\nconst text = norm(p.text);\nconst priced = [], check = [], dropped = [];\nfor (const it of Array.isArray(a.items) ? a.items : []) {\n  const label = it.requested || it.code || '(item)';\n  const q = norm(it.quote);\n  if (q.length < 2 || !text.includes(q)) { dropped.push('\"' + label + '\": the quoted words are not in the request'); continue; }\n  const qty = it.quantity === null || it.quantity === undefined || it.quantity === '' ? null : Number(it.quantity);\n  const qtyOk = qty !== null && Number.isFinite(qty) && qty > 0 && (numIn(it.quote, qty) || (qty === 1 && /一式|1式|a set|one set/i.test(String(it.quote).normalize('NFKC'))));\n  const item = it.code ? byCode.get(String(it.code)) : null;\n  const unitOk = !item || !it.unit || norm(it.unit) === norm(item.unit) || norm(item.aliases).includes(norm(it.unit));\n  if (item && qtyOk && unitOk && /^\\d+(\\.\\d{1,3})?$/.test(item.unit_price) && [0, 8, 10].includes(item.tax_rate)) {\n    priced.push({ code: item.code, name: item.name, quantity: qty, unit: item.unit, unit_price: item.unit_price, tax_rate: item.tax_rate, quote: it.quote });\n  } else {\n    const why = !item ? (it.code ? 'code ' + it.code + ' is not in the price list' : 'not in the price list') : !qtyOk ? (qty === null || !Number.isFinite(qty) ? 'no quantity written for it' : 'the quantity ' + qty + ' is not written in the request (the AI may have calculated it)') : !unitOk ? 'unit \"' + it.unit + '\" differs from the price list (' + item.unit + ')' : 'the price list row is not usable';\n    check.push({ requested: label, quantity: qty, unit: it.unit || '', why, quote: it.quote });\n  }\n}\n// The customer: the sender's e-mail first, then the company name written in the request (must match exactly one partner)\nconst from = String($('Config').first().json.from_email || '').toLowerCase();\nconst partners = p.partners.filter((x) => x.available !== false);\nlet partner = from ? partners.find((x) => String(x.email || '').toLowerCase() === from) : null;\nlet partnerBy = partner ? 'e-mail ' + from : '';\nif (!partner && a.customer_company) {\n  const want = norm(a.customer_company);\n  const hits = partners.filter((x) => [x.name, x.long_name].some((v) => v && norm(v) === want));\n  if (hits.length === 1 && text.includes(want)) { partner = hits[0]; partnerBy = 'company name \"' + a.customer_company + '\"'; }\n}\nconst base = { priced, check, dropped, customer_company: a.customer_company || null };\nif (!partner) return [{ json: { ok: false, ...base, problem: 'The customer was not found among the freee partners' + (a.customer_company ? ' (company name in the request: \"' + a.customer_company + '\")' : '') + (from ? ' (e-mail: ' + from + ')' : '') + '. Add the partner in freee (with this e-mail address), then run it again. No quotation was made.' } }];\nif (!priced.length) return [{ json: { ok: false, ...base, problem: 'No requested item could be priced from the price list, so no quotation was made.' } }];\n// Our own total, to compare with what freee calculates (tax rounded down per rate, as tax_fraction \"omit\")\nconst sub = {}; let net = 0;\nfor (const x of priced) { const v = Math.round(x.quantity * Number(x.unit_price)); net += v; sub[x.tax_rate] = (sub[x.tax_rate] || 0) + v; }\nconst tax = Object.entries(sub).reduce((s, [r, v]) => s + Math.floor(v * Number(r) / 100), 0);\nconst today = DateTime.now().setZone(c.timezone);\nconst lines = priced.map((x) => ({ type: 'item', description: x.name, quantity: x.quantity, unit: x.unit, unit_price: x.unit_price, tax_rate: x.tax_rate }))\n  .concat(check.map((x) => ({ type: 'text', description: ('※要確認 / To check: ' + x.requested + (x.quantity !== null ? ' ' + x.quantity + (x.unit || '') : '') + ' (' + x.why + ')').slice(0, 255) })));\nconst body = {\n  company_id: Number(c.freee_company_id), quotation_date: today.toFormat('yyyy-MM-dd'), expiration_date: today.plus({ days: Number(c.expiration_days) }).toFormat('yyyy-MM-dd'),\n  subject: String(a.subject || 'Quotation').slice(0, 100), tax_entry_method: c.tax_entry_method, tax_fraction: c.tax_fraction, withholding_tax_entry_method: 'out',\n  partner_id: partner.id, partner_title: partner.default_title && ['御中', '様', '(空白)'].includes(partner.default_title) ? partner.default_title : c.default_partner_title,\n  memo: ('Made by n8n from: ' + (c.from_email || c.source) + ' / ' + c.subject).slice(0, 2000), lines,\n};\nreturn [{ json: { ok: true, ...base, partner: { id: partner.id, name: partner.name, by: partnerBy }, expected: { net, tax, total: net + tax }, body } }];"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000013",
      "name": "Can make a quotation?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1980,
        150
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000014",
      "name": "Create the quotation in freee",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        50
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.freee.co.jp/iv/quotations",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000015",
      "name": "Write the notice",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2420,
        150
      ],
      "parameters": {
        "jsCode": "// One notice for every outcome: made (with the link), freee refused, or nothing made and why.\nconst c = $('Config').first().json;\nconst chk = $('Check and price').isExecuted ? $('Check and price').all(0)[0].json : null;\nconst yen = (v) => Number(v).toLocaleString('ja-JP') + ' yen';\nconst lines = [];\nlet title, ok = false;\nconst created = $('Create the quotation in freee').isExecuted ? $('Create the quotation in freee').all(0)[0].json : null;\nif (created && created.statusCode === 201) {\n  const q = created.body.quotation;\n  ok = true;\n  title = '[n8n quote] Quotation draft in freee: ' + q.partner_name + ' ' + yen(q.total_amount);\n  lines.push('A quotation was created in freee as UNSENT. Check it and send it from freee.', q.report_url || '', '');\n  lines.push(q.quotation_number + ' / ' + q.subject + ' / ' + q.partner_name + ' (' + chk.partner.by + ')');\n  chk.priced.forEach((x) => lines.push('- ' + x.name + ' ' + x.quantity + x.unit + ' x ' + yen(x.unit_price) + '  (from: \"' + x.quote + '\")'));\n  lines.push('Net ' + yen(q.amount_excluding_tax) + ', tax ' + yen(q.amount_tax) + ', total ' + yen(q.total_amount));\n  if (q.amount_excluding_tax !== chk.expected.net || q.amount_tax !== chk.expected.tax) lines.push('!! freee calculated different amounts than this workflow (net ' + yen(chk.expected.net) + ', tax ' + yen(chk.expected.tax) + '). Check the quotation.');\n} else {\n  title = '[n8n quote] No quotation was made';\n  if (created) lines.push('freee refused the quotation (HTTP ' + created.statusCode + '): ' + JSON.stringify(created.body && (created.body.errors || created.body)).slice(0, 400));\n  else lines.push($json.problem || 'Unknown problem.');\n}\nif (chk && chk.check.length) { lines.push('', 'To check (in the quotation as \"※要確認\" lines, no price):'); chk.check.forEach((x) => lines.push('- ' + x.requested + (x.quantity !== null ? ' ' + x.quantity + (x.unit || '') : '') + ': ' + x.why)); }\nif (chk && chk.dropped.length) { lines.push('', 'Left out (could not be traced to the request):'); chk.dropped.forEach((d) => lines.push('- ' + d)); }\nlines.push('', 'Request from ' + (c.from_email || c.source) + ': ' + c.subject);\nreturn [{ json: { ok, title, message: lines.join('\\n'), source: c.source, to: c.staff_email } }];"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000016",
      "name": "Answer where asked",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        2640,
        150
      ],
      "parameters": {
        "mode": "expression",
        "numberOutputs": 2,
        "output": "={{ $json.source === 'form' ? 0 : 1 }}"
      }
    },
    {
      "id": "c7000000-0000-4000-8000-000000000017",
      "name": "Show the result",
      "type": "n8n-nodes-base.form",
      "typeVersion": 2.5,
      "position": [
        2860,
        50
      ],
      "parameters": {
        "operation": "completion",
        "respondWith": "text",
        "completionTitle": "={{ $json.title }}",
        "completionMessage": "={{ $json.message }}"
      },
      "webhookId": "6a2d9e4c-1b7f-4c38-9e05-8f3b2a7d1c64"
    },
    {
      "id": "c7000000-0000-4000-8000-000000000018",
      "name": "Tell the staff",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2860,
        250
      ],
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $json.to }}",
        "subject": "={{ $json.title }}",
        "emailType": "text",
        "message": "={{ $json.message }}",
        "options": {
          "appendAttribution": false
        }
      }
    },
    {
      "parameters": {
        "content": "## Turn quote request mails into freee quotation drafts\nA request mail arrives (\"please quote 500 brochures and 2 staff days\"). **Claude lists the items and quantities and quotes the words behind each — it never sets a price.** **Code** checks the quotes and quantities, takes every unit price from **your price list sheet**, and finds the customer among your **freee partners**. A quotation is created in **freee Invoice as unsent**; a person checks it and sends it from freee.\n\nItems not in the price list, or quantities not written in the mail (for example \"2 people × 2 days\" turned into 4), become \"※要確認 / To check\" lines without a price.\n\n### Setup\n1. A freee app (freee Developers) with permissions for partners (read) and [請求書] all document types (read/write). Open freee Invoice once for the company.\n2. n8n credential: **OAuth2 API** for freee (auth URL https://accounts.secure.freee.co.jp/public_api/authorize, token URL .../public_api/token, authentication: Body). Also Gmail, Google Sheets, Anthropic.\n3. A sheet with the header: code, name, aliases, unit, unit_price, tax_rate.\n4. Fill **Config** and the Gmail search in **New request mail**. Try the form first.",
        "height": 1060,
        "width": 600,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -660,
        -300
      ],
      "id": "c7000000-0000-4000-9000-000000000018",
      "name": "Overview"
    },
    {
      "parameters": {
        "content": "## 1. A request comes in\nGmail (the search in the trigger decides which mails count) or the form.",
        "height": 620,
        "width": 400,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -20,
        -120
      ],
      "id": "c7000000-0000-4000-9000-000000000019",
      "name": "Section 1"
    },
    {
      "parameters": {
        "content": "## 2. Price list and partners\nThe sheet becomes a catalog (codes, names, other names, units; prices stay out of the prompt). Partners are read from freee.",
        "height": 620,
        "width": 840,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        420,
        -120
      ],
      "id": "c7000000-0000-4000-9000-000000000020",
      "name": "Section 2"
    },
    {
      "parameters": {
        "content": "## 3. Claude lists, code prices\nClaude returns code, quantity, unit and the exact words for each item. Code accepts an item only if the words are in the mail, the quantity is written in them and the unit matches; the price always comes from the sheet. The customer: sender e-mail first, then the company name written in the mail (exactly one match).",
        "height": 620,
        "width": 640,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1300,
        -120
      ],
      "id": "c7000000-0000-4000-9000-000000000021",
      "name": "Section 3"
    },
    {
      "parameters": {
        "content": "## 4. freee quotation (unsent)\nPOST to freee Invoice. Nothing is sent to the customer.",
        "height": 620,
        "width": 400,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1980,
        -120
      ],
      "id": "c7000000-0000-4000-9000-000000000022",
      "name": "Section 4"
    },
    {
      "parameters": {
        "content": "## 5. Tell the staff\nMail (or the form page) with the link, each line with the words it came from, what to check, and what was left out. A warning if freee's totals differ from the workflow's own.",
        "height": 620,
        "width": 680,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2400,
        -120
      ],
      "id": "c7000000-0000-4000-9000-000000000023",
      "name": "Section 5"
    }
  ],
  "connections": {
    "New request mail": {
      "main": [
        [
          {
            "node": "Read the mail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Paste a request": {
      "main": [
        [
          {
            "node": "Read the form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the mail": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the form": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Read the price list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the price list": {
      "main": [
        [
          {
            "node": "Read freee partners",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read freee partners": {
      "main": [
        [
          {
            "node": "Prepare the prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare the prompt": {
      "main": [
        [
          {
            "node": "Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ready?": {
      "main": [
        [
          {
            "node": "Read the items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Write the notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Haiku 4.5": {
      "ai_languageModel": [
        [
          {
            "node": "Read the items",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Read the items": {
      "main": [
        [
          {
            "node": "Check and price",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check and price": {
      "main": [
        [
          {
            "node": "Can make a quotation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Can make a quotation?": {
      "main": [
        [
          {
            "node": "Create the quotation in freee",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Write the notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create the quotation in freee": {
      "main": [
        [
          {
            "node": "Write the notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the notice": {
      "main": [
        [
          {
            "node": "Answer where asked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Answer where asked": {
      "main": [
        [
          {
            "node": "Show the result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tell the staff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}