{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "44918795-943b-46ec-8c23-a527f2146a23",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1888,
        -624
      ],
      "parameters": {
        "width": 448,
        "height": 764,
        "content": "## How it works\n\nThis workflow generates professional business proposals from your CRM deal data or form submissions. It validates pricing, adds your company branding, creates a beautiful PDF with itemized pricing and terms, then delivers it to the client via email while archiving a copy to Google Drive for your records. Your team gets a Slack notification with proposal details and a link to the Drive file.\n\n## Setup steps\n\n1. **Configure webhook** - Connect from your CRM (HubSpot/Pipedrive) or form tool\n2. **Customize branding** - Edit company details in \"Enrich with Company Data\" node\n3. **Add HTML to PDF API** - Get free key at htmlcsstoimage.com\n4. **Connect Google Drive** - Authenticate for proposal archival\n5. **Connect Gmail** - Authenticate for client email delivery\n6. **Add Slack webhook** - Get URL from Slack app settings\n7. **Test with sample data** before going live\n\n\ud83d\udca1 **Required data:** clientName, clientEmail, clientCompany, projectTitle, items array (with description, quantity, rate for each)"
      },
      "typeVersion": 1
    },
    {
      "id": "d9a7710c-0502-40a5-9eb7-ba487e50c859",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1088,
        64
      ],
      "parameters": {
        "path": "proposal-generator",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "id": "d69ca703-6d2a-4685-81ec-041a19ecaaa5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 664,
        "height": 368,
        "content": "## Data Processing\n\nReceives proposal data, validates required fields, calculates line item totals, adds your company branding and payment terms, then generates professional HTML with pricing breakdown."
      },
      "typeVersion": 1
    },
    {
      "id": "140d0a87-165f-466b-91d9-faa01147bbf5",
      "name": "Validate Input Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -864,
        64
      ],
      "parameters": {
        "jsCode": "// Validate incoming data\nconst item = $input.first().json;\n\n// Required fields validation\nconst requiredFields = ['clientName', 'clientEmail', 'clientCompany', 'projectTitle'];\nconst missingFields = requiredFields.filter(field => !item[field] || item[field].trim() === '');\n\n// Validate items array\nif (!item.items || !Array.isArray(item.items) || item.items.length === 0) {\n  missingFields.push('items (must be array with at least one item)');\n}\n\nif (missingFields.length > 0) {\n  throw new Error(`Missing required fields: ${missingFields.join(', ')}`);\n}\n\n// Validate email format\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nif (!emailRegex.test(item.clientEmail)) {\n  throw new Error('Invalid email format for clientEmail');\n}\n\n// Validate and calculate items\nfor (let i = 0; i < item.items.length; i++) {\n  const lineItem = item.items[i];\n  if (!lineItem.description || !lineItem.quantity || !lineItem.rate) {\n    throw new Error(`Item ${i + 1} missing required fields (description, quantity, rate)`);\n  }\n  item.items[i].amount = lineItem.amount || (lineItem.quantity * lineItem.rate);\n}\n\n// Calculate totals\nitem.subtotal = item.subtotal || item.items.reduce((sum, lineItem) => sum + lineItem.amount, 0);\nitem.discount = item.discount || 0;\nitem.tax = item.tax || 0;\nitem.total = item.total || (item.subtotal - item.discount + item.tax);\n\n// Generate proposal number if not provided\nif (!item.proposalNumber) {\n  const date = new Date();\n  const year = date.getFullYear();\n  const month = String(date.getMonth() + 1).padStart(2, '0');\n  const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');\n  item.proposalNumber = `PROP-${year}${month}-${random}`;\n}\n\n// Add defaults\nitem.currency = item.currency || 'USD';\nitem.currencySymbol = item.currencySymbol || '$';\nitem.proposalDate = item.proposalDate || new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\nitem.validUntil = item.validUntil || new Date(Date.now() + 30*24*60*60*1000).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\n\nreturn { json: item };"
      },
      "typeVersion": 2
    },
    {
      "id": "5f8f7b8e-cdf1-4401-be04-e162bcf118c9",
      "name": "Enrich with Company Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -656,
        64
      ],
      "parameters": {
        "jsCode": "// Add company branding and additional details\nconst item = $input.first().json;\n\n// Company information (customize these)\nconst companyDefaults = {\n  companyName: item.companyName || 'Media Jade',\n  companyAddress: item.companyAddress || '456 Company Street',\n  companyCity: item.companyCity || 'San Francisco, CA 94105',\n  companyEmail: item.companyEmail || 'user@example.com',\n  companyPhone: item.companyPhone || '+1234567890',\n  companyWebsite: item.companyWebsite || 'www.mediajde.com',\n  companyLogo: item.companyLogo || '',\n  \n  // Proposal terms\n  paymentTerms: item.paymentTerms || '50% upfront, 50% upon completion',\n  deliveryTimeline: item.deliveryTimeline || '4-6 weeks from project kickoff',\n  terms: item.terms || 'This proposal is valid for 30 days. Upon acceptance, a detailed contract will be provided. All work will be completed according to agreed specifications and timelines.',\n  \n  // Client defaults\n  clientPhone: item.clientPhone || '',\n  clientAddress: item.clientAddress || '',\n  clientCity: item.clientCity || ''\n};\n\nreturn { json: { ...item, ...companyDefaults } };"
      },
      "typeVersion": 2
    },
    {
      "id": "1bc12998-5f3a-4dd0-afb7-2f8a6fccd8cc",
      "name": "Generate Professional HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -432,
        64
      ],
      "parameters": {
        "jsCode": "// Generate professional HTML proposal\nconst item = $input.first().json;\n\nconst html = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Proposal - ${item.proposalNumber}</title>\n    <style>\n        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        body { font-family: 'Inter', sans-serif; line-height: 1.6; color: #1f2937; }\n        .container { max-width: 800px; margin: 0 auto; padding: 50px 40px; }\n        .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 50px; padding-bottom: 30px; border-bottom: 4px solid #3b82f6; }\n        .logo { max-width: 180px; height: auto; }\n        .company-name { color: #3b82f6; font-size: 28px; font-weight: 700; margin-bottom: 10px; }\n        .company-details { font-size: 13px; color: #6b7280; line-height: 1.8; }\n        .proposal-badge { text-align: right; }\n        .proposal-title { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: white; padding: 15px 30px; border-radius: 8px; font-size: 24px; font-weight: 700; letter-spacing: 2px; margin-bottom: 15px; box-shadow: 0 4px 6px rgba(59, 130, 246, 0.3); }\n        .proposal-meta { font-size: 13px; color: #6b7280; text-align: right; }\n        .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 25px; margin-bottom: 50px; }\n        .info-card { background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); padding: 25px; border-radius: 12px; border: 1px solid #e2e8f0; }\n        .info-card h3 { color: #3b82f6; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #3b82f6; }\n        .info-card p { font-size: 14px; color: #4b5563; margin: 8px 0; }\n        .info-card .name { font-size: 18px; font-weight: 600; color: #1f2937; margin-bottom: 5px; }\n        .section { margin-bottom: 45px; }\n        .section-title { color: #1e40af; font-size: 22px; font-weight: 700; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 3px solid #dbeafe; }\n        .section-content { color: #4b5563; font-size: 15px; line-height: 1.9; }\n        .items-table { width: 100%; border-collapse: separate; border-spacing: 0; margin-bottom: 30px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); border-radius: 12px; overflow: hidden; }\n        .items-table thead { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); }\n        .items-table th { padding: 16px; text-align: left; font-weight: 600; font-size: 14px; color: white; text-transform: uppercase; }\n        .items-table tbody tr { background: white; }\n        .items-table tbody tr:nth-child(even) { background: #f9fafb; }\n        .items-table tbody tr:hover { background: #eff6ff; }\n        .items-table td { padding: 16px; border-bottom: 1px solid #e5e7eb; font-size: 14px; color: #374151; }\n        .items-table tbody tr:last-child td { border-bottom: none; }\n        .text-right { text-align: right; }\n        .pricing-summary { margin-left: auto; width: 400px; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); padding: 25px; border-radius: 12px; border: 2px solid #e2e8f0; }\n        .pricing-row { display: flex; justify-content: space-between; padding: 12px 0; font-size: 15px; color: #4b5563; }\n        .pricing-row.total { border-top: 3px solid #3b82f6; margin-top: 15px; padding-top: 20px; font-weight: 700; font-size: 20px; color: #1e40af; }\n        .terms-section { margin-top: 50px; padding: 30px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-left: 6px solid #f59e0b; border-radius: 8px; }\n        .terms-section h3 { color: #92400e; font-size: 18px; font-weight: 700; margin-bottom: 20px; }\n        .term-item { background: rgba(255, 255, 255, 0.6); padding: 15px; border-radius: 6px; margin-bottom: 10px; }\n        .term-item strong { color: #78350f; display: block; margin-bottom: 5px; font-size: 14px; }\n        .term-item p { color: #92400e; font-size: 14px; margin: 0; }\n        .footer { margin-top: 60px; padding-top: 30px; border-top: 3px solid #e5e7eb; text-align: center; }\n        .footer-content { background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); padding: 30px; border-radius: 12px; }\n        .footer h4 { color: #1e40af; font-size: 20px; margin-bottom: 15px; }\n        .footer p { color: #4b5563; font-size: 14px; line-height: 1.8; margin: 8px 0; }\n        .footer .contact { color: #3b82f6; font-weight: 600; }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <div>\n                ${item.companyLogo ? `<img src=\"${item.companyLogo}\" alt=\"${item.companyName}\" class=\"logo\">` : `<h1 class=\"company-name\">${item.companyName}</h1>`}\n                <div class=\"company-details\">\n                    ${item.companyAddress}<br>\n                    ${item.companyCity}<br>\n                    ${item.companyEmail}<br>\n                    ${item.companyPhone}\n                </div>\n            </div>\n            <div class=\"proposal-badge\">\n                <div class=\"proposal-title\">PROPOSAL</div>\n                <div class=\"proposal-meta\"><strong>Date:</strong> ${item.proposalDate}</div>\n            </div>\n        </div>\n\n        <div class=\"info-grid\">\n            <div class=\"info-card\">\n                <h3>Prepared For</h3>\n                <p class=\"name\">${item.clientName}</p>\n                <p><strong>${item.clientCompany}</strong></p>\n                <p>${item.clientEmail}</p>\n                ${item.clientPhone ? `<p>${item.clientPhone}</p>` : ''}\n                ${item.clientAddress ? `<p>${item.clientAddress}</p>` : ''}\n            </div>\n            <div class=\"info-card\">\n                <h3>Proposal Details</h3>\n                <p><strong>Proposal #:</strong> ${item.proposalNumber}</p>\n                <p><strong>Date:</strong> ${item.proposalDate}</p>\n                <p><strong>Valid Until:</strong> ${item.validUntil}</p>\n            </div>\n        </div>\n\n        <div class=\"section\">\n            <h2 class=\"section-title\">${item.projectTitle}</h2>\n            <div class=\"section-content\">${item.projectDescription || 'Professional services as discussed.'}</div>\n        </div>\n\n        <div class=\"section\">\n            <h2 class=\"section-title\">Investment Breakdown</h2>\n            <table class=\"items-table\">\n                <thead>\n                    <tr>\n                        <th style=\"width: 50%;\">Description</th>\n                        <th class=\"text-right\" style=\"width: 15%;\">Quantity</th>\n                        <th class=\"text-right\" style=\"width: 17%;\">Rate</th>\n                        <th class=\"text-right\" style=\"width: 18%;\">Amount</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    ${item.items.map(lineItem => `\n                    <tr>\n                        <td>${lineItem.description}</td>\n                        <td class=\"text-right\">${lineItem.quantity}</td>\n                        <td class=\"text-right\">${item.currencySymbol}${lineItem.rate.toFixed(2)}</td>\n                        <td class=\"text-right\"><strong>${item.currencySymbol}${lineItem.amount.toFixed(2)}</strong></td>\n                    </tr>\n                    `).join('')}\n                </tbody>\n            </table>\n\n            <div class=\"pricing-summary\">\n                <div class=\"pricing-row\">\n                    <span>Subtotal:</span>\n                    <span>${item.currencySymbol}${item.subtotal.toFixed(2)}</span>\n                </div>\n                ${item.discount > 0 ? `<div class=\"pricing-row\"><span>Discount:</span><span>-${item.currencySymbol}${item.discount.toFixed(2)}</span></div>` : ''}\n                ${item.tax > 0 ? `<div class=\"pricing-row\"><span>Tax:</span><span>${item.currencySymbol}${item.tax.toFixed(2)}</span></div>` : ''}\n                <div class=\"pricing-row total\">\n                    <span>Total Investment:</span>\n                    <span>${item.currencySymbol}${item.total.toFixed(2)}</span>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"terms-section\">\n            <h3>Terms & Conditions</h3>\n            <div class=\"term-item\">\n                <strong>Payment Terms:</strong>\n                <p>${item.paymentTerms}</p>\n            </div>\n            <div class=\"term-item\">\n                <strong>Delivery Timeline:</strong>\n                <p>${item.deliveryTimeline}</p>\n            </div>\n            <div class=\"term-item\">\n                <strong>Additional Terms:</strong>\n                <p>${item.terms}</p>\n            </div>\n        </div>\n\n        <div class=\"footer\">\n            <div class=\"footer-content\">\n                <h4>Thank You for Considering Our Proposal!</h4>\n                <p>For questions, please contact us:</p>\n                <p class=\"contact\">${item.companyEmail} | ${item.companyPhone}</p>\n                <p style=\"margin-top: 15px; font-size: 13px; color: #6b7280;\">Valid until ${item.validUntil}</p>\n            </div>\n        </div>\n    </div>\n</body>\n</html>\n`;\n\nreturn {\n  json: {\n    html: html,\n    proposalNumber: item.proposalNumber,\n    clientName: item.clientName,\n    clientEmail: item.clientEmail,\n    clientCompany: item.clientCompany,\n    total: item.total,\n    currency: item.currency,\n    fileName: `Proposal_${item.proposalNumber}_${item.clientCompany.replace(/[^a-zA-Z0-9]/g, '_')}.pdf`,\n    ...item\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "22d9be67-cdbd-4597-b9f6-d60be3f14fdb",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -272,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 360,
        "content": "## PDF & Storage\n\nConverts HTML to print-ready PDF, then archives to Google Drive with shareable link for easy client access and permanent record keeping."
      },
      "typeVersion": 1
    },
    {
      "id": "ef43aebf-942b-4a5d-ae55-9647c0a6f690",
      "name": "Save to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -16,
        64
      ],
      "parameters": {
        "name": "={{ $('Generate Professional HTML').item.json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "1ec57955-95af-4a2d-a53c-0b4b594762a9",
      "name": "Notify Team (Slack)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        528,
        64
      ],
      "parameters": {
        "url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
        "options": {},
        "jsonBody": "={\n  \"text\": \"\u2705 New Proposal Sent!\",\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*\ud83d\udce8 Proposal Successfully Sent*\\n\\n*Proposal #:* {{ $('Generate Professional HTML').item.json.proposalNumber }}\\n*Client:* {{ $('Generate Professional HTML').item.json.clientCompany }}\\n*Contact:* {{ $('Generate Professional HTML').item.json.clientName }}\\n*Total:* {{ $('Generate Professional HTML').item.json.currency }} {{ $('Generate Professional HTML').item.json.total }}\\n\\n<{{ $('Save to Google Drive').item.json.webViewLink }}|View PDF in Drive>\"\n      }\n    ]\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.1
    },
    {
      "id": "9bdc7dfa-d144-41bb-a3c1-eb7883a374f9",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        448,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 308,
        "height": 380,
        "content": "## Team Notification\n\nSlack alert with proposal number, client details, total amount, and Drive link. Keeps your sales team informed in real-time."
      },
      "typeVersion": 1
    },
    {
      "id": "18e667ce-b6fa-45b5-afcc-4e39a406850e",
      "name": "HTML to PDF",
      "type": "n8n-nodes-htmlcsstopdf.htmlcsstopdf",
      "position": [
        -224,
        64
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "828c5819-e669-4427-8411-91bb506f335a",
      "name": "Send to Client",
      "type": "n8n-nodes-base.gmail",
      "position": [
        208,
        64
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c565c5a3-a658-4a90-b3e5-0b7b060bc14b",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 260,
        "height": 364,
        "content": "## Client Delivery\n\nEmails professional proposal PDF to client with your branding. Instant delivery accelerates your sales cycle and improves response rates."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "HTML to PDF": {
      "main": [
        [
          {
            "node": "Save to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Client": {
      "main": [
        [
          {
            "node": "Notify Team (Slack)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Validate Input Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input Data": {
      "main": [
        [
          {
            "node": "Enrich with Company Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Google Drive": {
      "main": [
        [
          {
            "node": "Send to Client",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich with Company Data": {
      "main": [
        [
          {
            "node": "Generate Professional HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Professional HTML": {
      "main": [
        [
          {
            "node": "HTML to PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}