AutomationFlowsWeb Scraping › CRM to Professional PDF Proposals with Gmail, Drive & Slack Notifications

CRM to Professional PDF Proposals with Gmail, Drive & Slack Notifications

ByJitesh Dugar @jiteshdugar on n8n.io

Transform proposal creation from hours to minutes - automatically generate beautifully designed PDF proposals from CRM data or form submissions, deliver them instantly via email, store in Google Drive, and notify your sales team - all without lifting a finger.

Webhook trigger★★★★☆ complexity13 nodesGoogle DriveHTTP RequestN8N Nodes HtmlcsstopdfGmail
Web Scraping Trigger: Webhook Nodes: 13 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #10584 — we link there as the canonical source.

This workflow follows the Gmail → Google Drive recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "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
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Transform proposal creation from hours to minutes - automatically generate beautifully designed PDF proposals from CRM data or form submissions, deliver them instantly via email, store in Google Drive, and notify your sales team - all without lifting a finger.

Source: https://n8n.io/workflows/10584/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

A comprehensive n8n workflow template that completely automates the startup pitch deck submission process for accelerators, incubators, VC firms, and startup competitions. This workflow validates foun

Google Drive, Gmail, N8N Nodes Verifiemail +2
Web Scraping

This workflow automates the entire parent consent process for school field trips, replacing manual paper forms with a secure, verified, and legally compliant digital system.

Google Drive, Gmail, N8N Nodes Verifiemail +2
Web Scraping

Transform new hire onboarding from 3-4 hours of manual document compilation to 3 minutes of automated generation - creates personalized, role-specific document packages including welcome letters, bene

N8N Nodes Htmlcsstopdf, Google Drive, Gmail +1
Web Scraping

Automatically generate professional PDF invoices when new orders are placed in Shopify. This template creates beautifully formatted invoices from order data, converts them to PDF, saves to Google Driv

Google Drive, Gmail, N8N Nodes Htmlcsstopdf +1
Web Scraping

Verified Corporate Training Certificate with CEUs – Fully Automated & Verifiable

Google Drive, Slack, Google Sheets +4