This workflow corresponds to n8n.io template #16491 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request 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 →
{
"id": "TyRa6OQry8lTHZFw",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Generate and send personalized client proposals by client type via email with AI",
"tags": [],
"nodes": [
{
"id": "906e7e49-1fa0-4d21-9bfe-4f258e72e536",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-80,
208
],
"parameters": {
"width": 880,
"height": 820,
"content": "## Generate and send personalized client proposals by client type via email with AI\n\n### Who's it for\n- Sales teams sending 10+ proposals per week who need consistent, branded output\n- Freelancers and agencies automating their proposal pipeline from a CRM or web form\n- Business development teams wanting AI-written proposals without manual copy work\n- Service businesses with multiple client tiers needing different pricing and tone per segment\n\n### How it works\n1. Client submits a request via webhook POST from a CRM, contact form, or API\n2. Alternatively, a scheduler polls a form submission queue every hour\n3. All field name variants are normalised into a consistent schema\n4. JS validates required fields and classifies the budget into a tier (starter / growth / enterprise / enterprise+)\n5. Invalid requests receive a polite error email with the missing fields listed\n6. A Switch node routes valid requests to one of five templates based on client type: startup, enterprise, agency, nonprofit, or standard\n7. Conditional pricing logic applies base prices, line items per service, and client-type discounts\n8. AI generates a 7-section personalized proposal using the client data, pricing, and template context\n9. A JS node formats the proposal into a branded HTML email with an investment breakdown table\n10. The proposal is sent to the client via SendGrid, logged to Google Sheets, and a Slack alert is sent to the team\n11. A 200 success response is returned to the webhook caller\n\n### How to customize\n- Add new client types by creating a new Set node with template fields and adding a condition to the Switch node\n- Adjust pricing tiers and base rates in `JS - Conditional Pricing Logic` under the `basePrices` object\n- Change discount rules per client type in the same node \u2014 currently nonprofits get 20% and early-stage startups get 10%\n- Modify the 7-section proposal structure by editing the section headings in the AI prompt inside `AI - Generate Proposal Copy`\n- Add a PDF generation step after `JS - Format Proposal Document` using an HTML-to-PDF service like Puppeteer or WeasyPrint\n- Swap SendGrid for the native Gmail node if you prefer Google Workspace delivery\n- Extend the Google Sheet columns to track proposal acceptance status and follow-up dates"
},
"typeVersion": 1
},
{
"id": "7741b45d-f9f2-4065-a8f2-02e0e50782f3",
"name": "Sticky Note - Trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
432
],
"parameters": {
"color": 4,
"width": 764,
"height": 500,
"content": "## 1. Trigger & Intake\n\nAccepts POST from any CRM, form, or API.\nAlso polls hourly as a fallback.\nNormalizes all field name variants."
},
"typeVersion": 1
},
{
"id": "7afff03e-dc30-4181-b123-e6102b56c965",
"name": "Sticky Note - Validate",
"type": "n8n-nodes-base.stickyNote",
"position": [
1808,
384
],
"parameters": {
"color": 4,
"width": 764,
"height": 1124,
"content": "## 2. Validate & Classify\n\nChecks required fields, converts budget to numeric, assigns budgetTier. Invalid requests branch to a polite error email."
},
"typeVersion": 1
},
{
"id": "0c7a8723-aac6-44a8-b599-78a28382dde6",
"name": "Sticky Note - AI Generation",
"type": "n8n-nodes-base.stickyNote",
"position": [
2656,
448
],
"parameters": {
"color": 4,
"width": 708,
"height": 620,
"content": "## 3. Template Selection & AI Generation\n\nSwitch routes by clientType to one of 5 templates. Pricing logic applies discounts and builds line items. AI generates a 7-section personalized proposal."
},
"typeVersion": 1
},
{
"id": "ea7a829e-9a1f-49cf-a9f2-784fa83efe00",
"name": "Sticky Note - Deliver",
"type": "n8n-nodes-base.stickyNote",
"position": [
3472,
336
],
"parameters": {
"color": 4,
"width": 1196,
"height": 796,
"content": "## 4. Deliver & Track\n\nFormats branded HTML proposal, sends to client via SendGrid, logs to Google Sheets, notifies team on Slack, and returns a webhook response."
},
"typeVersion": 1
},
{
"id": "0e113acb-cf08-4002-a087-e507bfa880e7",
"name": "Webhook - New Proposal Request",
"type": "n8n-nodes-base.webhook",
"position": [
1056,
592
],
"parameters": {
"path": "proposal-request",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "1b00d593-ad8e-4532-80f9-debe72c2a94b",
"name": "Poll - Hourly Form Submissions",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1056,
768
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 * * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "8a613abb-ce48-46fc-80b7-3bcd398b190b",
"name": "Normalize Client Data",
"type": "n8n-nodes-base.set",
"position": [
1248,
688
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f001",
"name": "clientName",
"type": "string",
"value": "={{ $json.clientName || $json.body?.clientName || $json.body?.client_name || '' }}"
},
{
"id": "f002",
"name": "clientEmail",
"type": "string",
"value": "={{ $json.clientEmail || $json.body?.clientEmail || $json.body?.client_email || '' }}"
},
{
"id": "f003",
"name": "companyName",
"type": "string",
"value": "={{ $json.companyName || $json.body?.companyName || $json.body?.company || 'N/A' }}"
},
{
"id": "f004",
"name": "projectScope",
"type": "string",
"value": "={{ $json.projectScope || $json.body?.projectScope || $json.body?.scope || '' }}"
},
{
"id": "f005",
"name": "servicesNeeded",
"type": "string",
"value": "={{ $json.servicesNeeded || $json.body?.servicesNeeded || $json.body?.services || '' }}"
},
{
"id": "f006",
"name": "budget",
"type": "string",
"value": "={{ $json.budget || $json.body?.budget || '0' }}"
},
{
"id": "f007",
"name": "timeline",
"type": "string",
"value": "={{ $json.timeline || $json.body?.timeline || '30 days' }}"
},
{
"id": "f008",
"name": "clientType",
"type": "string",
"value": "={{ ($json.clientType || $json.body?.clientType || $json.body?.client_type || 'standard').toLowerCase() }}"
},
{
"id": "f009",
"name": "industry",
"type": "string",
"value": "={{ $json.industry || $json.body?.industry || 'General' }}"
},
{
"id": "f010",
"name": "submittedAt",
"type": "string",
"value": "={{ new Date().toISOString() }}"
},
{
"id": "f011",
"name": "requestId",
"type": "string",
"value": "={{ 'PRO-' + Date.now().toString().slice(-8) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4eeb75f1-0fd3-418e-a976-de6160c70973",
"name": "JS - Validate and Classify",
"type": "n8n-nodes-base.code",
"position": [
1584,
688
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst errors = [];\nconst required = ['clientName', 'clientEmail', 'projectScope', 'budget'];\nfor (const field of required) {\n if (!item[field] || item[field].toString().trim() === '') {\n errors.push('Missing required field: ' + field);\n }\n}\nconst budgetRaw = String(item.budget || '0').replace(/[,$\\s]/g, '');\nconst budgetNumeric = parseFloat(budgetRaw) || 0;\nlet budgetTier = 'starter';\nif (budgetNumeric >= 50000) budgetTier = 'enterprise_plus';\nelse if (budgetNumeric >= 10000) budgetTier = 'enterprise';\nelse if (budgetNumeric >= 1000) budgetTier = 'growth';\nreturn {\n json: {\n ...item,\n budgetNumeric,\n budgetTier,\n isValid: errors.length === 0,\n validationErrors: errors\n }\n};"
},
"typeVersion": 2
},
{
"id": "f998ca29-463a-4a0b-a8d2-1feb2aa828b9",
"name": "Filter - Valid Requests Only",
"type": "n8n-nodes-base.filter",
"position": [
1872,
688
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-valid-001",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.isValid }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4ce22f88-8855-45c0-91c9-8a756741be61",
"name": "Send Validation Error Notice",
"type": "n8n-nodes-base.httpRequest",
"position": [
2176,
1056
],
"parameters": {
"url": "https://api.sendgrid.com/v3/mail/send",
"method": "POST",
"options": {},
"jsonBody": "={\n \"personalizations\": [{\"to\": [{\"email\": \"{{ $json.clientEmail }}\"}]}],\n \"from\": {\"email\": \"proposals@yourcompany.com\", \"name\": \"Your Company\"},\n \"subject\": \"Missing info on your proposal request\",\n \"content\": [{\"type\": \"text/plain\", \"value\": \"Hi {{ $json.clientName }},\\n\\nWe received your request but some required information was missing: {{ $json.validationErrors.join(', ') }}\\n\\nPlease resubmit with complete details.\\n\\nBest regards,\\nThe Proposals Team\"}]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "1234e213-cf8c-4c6f-9031-49e12cfdb6c7",
"name": "Switch - Client Type Router",
"type": "n8n-nodes-base.switch",
"position": [
2080,
672
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "startup",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "sw-cond-001",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.clientType }}",
"rightValue": "startup"
}
]
},
"renameOutput": true
},
{
"outputKey": "enterprise",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "sw-cond-002",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.clientType }}",
"rightValue": "enterprise"
}
]
},
"renameOutput": true
},
{
"outputKey": "agency",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "sw-cond-003",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.clientType }}",
"rightValue": "agency"
}
]
},
"renameOutput": true
},
{
"outputKey": "nonprofit",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "sw-cond-004",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.clientType }}",
"rightValue": "nonprofit"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3.2
},
{
"id": "8cb0449c-32bf-47bc-9458-9a62add27298",
"name": "Template - Startup",
"type": "n8n-nodes-base.set",
"position": [
2368,
528
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "t1f1",
"name": "templateName",
"type": "string",
"value": "Startup Growth Package"
},
{
"id": "t1f2",
"name": "templateId",
"type": "string",
"value": "TPL-STARTUP-001"
},
{
"id": "t1f3",
"name": "headerColor",
"type": "string",
"value": "#6C63FF"
},
{
"id": "t1f4",
"name": "defaultPaymentTerms",
"type": "string",
"value": "50% upfront, 50% on delivery"
},
{
"id": "t1f5",
"name": "defaultWarranty",
"type": "string",
"value": "30-day bug-fix warranty included"
},
{
"id": "t1f6",
"name": "templateFocus",
"type": "string",
"value": "agile delivery, lean MVP, fast time-to-market"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "02a62613-2b75-4a71-afbc-5c66202872de",
"name": "Template - Enterprise",
"type": "n8n-nodes-base.set",
"position": [
2384,
704
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "t2f1",
"name": "templateName",
"type": "string",
"value": "Enterprise Solutions Package"
},
{
"id": "t2f2",
"name": "templateId",
"type": "string",
"value": "TPL-ENT-001"
},
{
"id": "t2f3",
"name": "headerColor",
"type": "string",
"value": "#1A365D"
},
{
"id": "t2f4",
"name": "defaultPaymentTerms",
"type": "string",
"value": "Net 30, milestone-based billing"
},
{
"id": "t2f5",
"name": "defaultWarranty",
"type": "string",
"value": "90-day SLA with dedicated support"
},
{
"id": "t2f6",
"name": "templateFocus",
"type": "string",
"value": "scalability, security, compliance, enterprise integration"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "c9baed48-0f6a-4bf5-9e7c-32db9addadf1",
"name": "Template - Agency",
"type": "n8n-nodes-base.set",
"position": [
2384,
880
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "t3f1",
"name": "templateName",
"type": "string",
"value": "Agency White-Label Package"
},
{
"id": "t3f2",
"name": "templateId",
"type": "string",
"value": "TPL-AGENCY-001"
},
{
"id": "t3f3",
"name": "headerColor",
"type": "string",
"value": "#2D6A4F"
},
{
"id": "t3f4",
"name": "defaultPaymentTerms",
"type": "string",
"value": "Monthly retainer or project-based"
},
{
"id": "t3f5",
"name": "defaultWarranty",
"type": "string",
"value": "Unlimited revisions within project scope"
},
{
"id": "t3f6",
"name": "templateFocus",
"type": "string",
"value": "white-label delivery, reseller margins, quick turnaround"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a51b47c7-955c-4f0a-a62e-c0e9f521e0f9",
"name": "Template - Nonprofit",
"type": "n8n-nodes-base.set",
"position": [
2384,
1040
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "t4f1",
"name": "templateName",
"type": "string",
"value": "Nonprofit Impact Package"
},
{
"id": "t4f2",
"name": "templateId",
"type": "string",
"value": "TPL-NPO-001"
},
{
"id": "t4f3",
"name": "headerColor",
"type": "string",
"value": "#E07B39"
},
{
"id": "t4f4",
"name": "defaultPaymentTerms",
"type": "string",
"value": "Grant-cycle aligned billing, flexible terms"
},
{
"id": "t4f5",
"name": "defaultWarranty",
"type": "string",
"value": "60-day support, discounted rate on extensions"
},
{
"id": "t4f6",
"name": "templateFocus",
"type": "string",
"value": "mission-driven value, social impact, cost-efficiency"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6d6b1581-4c0f-4831-aa4c-0828324de4d6",
"name": "Template - Standard Default",
"type": "n8n-nodes-base.set",
"position": [
2384,
1232
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "t5f1",
"name": "templateName",
"type": "string",
"value": "Standard Business Package"
},
{
"id": "t5f2",
"name": "templateId",
"type": "string",
"value": "TPL-STD-001"
},
{
"id": "t5f3",
"name": "headerColor",
"type": "string",
"value": "#334155"
},
{
"id": "t5f4",
"name": "defaultPaymentTerms",
"type": "string",
"value": "50% upfront, 50% on delivery"
},
{
"id": "t5f5",
"name": "defaultWarranty",
"type": "string",
"value": "30-day support included"
},
{
"id": "t5f6",
"name": "templateFocus",
"type": "string",
"value": "reliable delivery, clear milestones, professional quality"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e569f898-3f49-45ec-bbc6-f2a96ef4a198",
"name": "JS - Conditional Pricing Logic",
"type": "n8n-nodes-base.code",
"position": [
2736,
688
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst tier = item.budgetTier || 'starter';\nconst clientType = (item.clientType || 'standard').toLowerCase();\nconst services = (item.servicesNeeded || '').toLowerCase();\n\nconst basePrices = {\n starter: { setup: 500, monthly: 200, hourly: 75 },\n growth: { setup: 2000, monthly: 800, hourly: 100 },\n enterprise: { setup: 8000, monthly: 3000, hourly: 150 },\n enterprise_plus: { setup: 20000, monthly: 8000, hourly: 200 }\n};\nconst price = basePrices[tier] || basePrices['starter'];\n\nlet discountPct = 0;\nif (clientType === 'nonprofit') discountPct = 20;\nelse if (clientType === 'startup' && (item.budgetNumeric || 0) < 5000) discountPct = 10;\nconst discountLabel = discountPct > 0 ? (discountPct + '% ' + clientType + ' discount applied') : 'No discount';\n\nconst lineItems = [];\nif (services.includes('design') || services.includes('ui') || services.includes('ux')) {\n lineItems.push({ service: 'UI/UX Design', price: price.hourly * 20 });\n}\nif (services.includes('development') || services.includes('dev') || services.includes('code')) {\n lineItems.push({ service: 'Software Development', price: price.hourly * 40 });\n}\nif (services.includes('seo') || services.includes('marketing')) {\n lineItems.push({ service: 'SEO & Digital Marketing', price: price.monthly });\n}\nif (services.includes('consulting') || services.includes('strategy')) {\n lineItems.push({ service: 'Strategy & Consulting', price: price.hourly * 10 });\n}\nif (services.includes('maintenance') || services.includes('support')) {\n lineItems.push({ service: 'Ongoing Support & Maintenance', price: price.monthly });\n}\nif (lineItems.length === 0) {\n lineItems.push({ service: 'Custom Project Delivery', price: price.setup });\n}\n\nconst subtotal = lineItems.reduce(function(sum, l) { return sum + l.price; }, 0);\nconst discountAmount = Math.round(subtotal * (discountPct / 100));\nconst total = subtotal - discountAmount;\n\nreturn {\n json: {\n ...item,\n pricingTier: tier,\n lineItems: lineItems,\n subtotal: subtotal,\n discountPct: discountPct,\n discountAmount: discountAmount,\n discountLabel: discountLabel,\n totalPrice: total,\n formattedTotal: '$' + total.toLocaleString(),\n currencyCode: 'USD'\n }\n};"
},
"typeVersion": 2
},
{
"id": "1a0aeea8-60a4-4b54-acc1-17c8316fec14",
"name": "Wait - Rate Limit Buffer",
"type": "n8n-nodes-base.wait",
"position": [
2960,
688
],
"parameters": {
"amount": 2
},
"typeVersion": 1.1
},
{
"id": "6f0e0885-5311-4b55-80fe-5167cec9fdaa",
"name": "AI - Generate Proposal Copy",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
3120,
688
],
"parameters": {
"text": "=You are an expert business proposal writer. Write a professional, persuasive, and personalized proposal for the client below. It must feel tailored \u2014 not generic.\n\n--- CLIENT ---\nName: {{ $json.clientName }}\nCompany: {{ $json.companyName }}\nIndustry: {{ $json.industry }}\nClient Type: {{ $json.clientType }}\n\n--- PROJECT ---\nScope: {{ $json.projectScope }}\nServices: {{ $json.servicesNeeded }}\nTimeline: {{ $json.timeline }}\nBudget: {{ $json.budget }}\n\n--- PRICING ---\nTotal: {{ $json.formattedTotal }}\nDiscount: {{ $json.discountLabel }}\nPayment Terms: {{ $json.defaultPaymentTerms }}\n\n--- TEMPLATE CONTEXT ---\nFocus: {{ $json.templateFocus }}\nWarranty: {{ $json.defaultWarranty }}\n\nWrite the proposal with these exact section headings:\n1. Executive Summary\n2. Our Understanding of Your Needs\n3. Proposed Solution and Deliverables\n4. Timeline and Milestones\n5. Investment Summary\n6. Why Choose Us\n7. Next Steps\n\nTone: professional but warm. No jargon. Maximum 500 words total.",
"options": {},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "0778cdf9-114d-4f5e-aa37-881db7835a60",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
3216,
912
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "ccff6f9a-50df-47c3-875f-8700002e2592",
"name": "JS - Format Proposal Document",
"type": "n8n-nodes-base.code",
"position": [
3568,
688
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst proposalText = item.output || item.response || item.text || 'Proposal content unavailable.';\n\nconst lineItemRows = (item.lineItems || []).map(function(l) {\n return '<tr><td style=\"padding:8px 12px;border-bottom:1px solid #e2e8f0\">' + l.service + '</td><td style=\"padding:8px 12px;border-bottom:1px solid #e2e8f0;text-align:right\">$' + l.price.toLocaleString() + '</td></tr>';\n}).join('');\n\nconst discountRow = item.discountAmount > 0\n ? '<tr><td style=\"padding:8px 12px;color:#16a34a\">Discount (' + item.discountLabel + ')</td><td style=\"padding:8px 12px;color:#16a34a;text-align:right\">-$' + item.discountAmount.toLocaleString() + '</td></tr>'\n : '';\n\nconst hColor = item.headerColor || '#334155';\nconst today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\n\nconst htmlProposal = '<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>body{font-family:Segoe UI,Arial,sans-serif;color:#1e293b;margin:0;padding:0;background:#f8fafc}.wrapper{max-width:720px;margin:32px auto;background:#fff;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.08);overflow:hidden}.header{background:' + hColor + ';color:#fff;padding:36px 40px}.header h1{margin:0 0 6px;font-size:26px;font-weight:700}.header p{margin:0;opacity:.85;font-size:15px}.meta{background:#f1f5f9;padding:16px 40px;font-size:13px;color:#64748b}.body{padding:36px 40px;white-space:pre-wrap;line-height:1.8;font-size:15px}.ptable{width:100%;border-collapse:collapse;margin:24px 0;font-size:14px}.ptable th{background:#f1f5f9;padding:10px 12px;text-align:left;font-weight:600;color:#475569}.trow td{font-weight:700;font-size:16px;padding:12px;background:#f8fafc}.footer{background:#f8fafc;padding:20px 40px;font-size:12px;color:#94a3b8;border-top:1px solid #e2e8f0;text-align:center}.cta{text-align:center;margin:32px 0 8px}.cta a{background:' + hColor + ';color:#fff;text-decoration:none;padding:14px 32px;border-radius:8px;font-weight:600;font-size:15px}</style></head><body><div class=\"wrapper\"><div class=\"header\"><h1>Business Proposal</h1><p>Prepared for ' + item.clientName + ' · ' + item.companyName + '</p></div><div class=\"meta\"><strong>Proposal ID:</strong> ' + item.requestId + ' <strong>Date:</strong> ' + today + ' <strong>Template:</strong> ' + item.templateName + ' <strong>Timeline:</strong> ' + item.timeline + '</div><div class=\"body\">' + proposalText + '</div><div style=\"padding:0 40px 24px\"><h3 style=\"color:#475569;font-size:13px;text-transform:uppercase;letter-spacing:.05em\">Investment Breakdown</h3><table class=\"ptable\"><thead><tr><th>Service</th><th style=\"text-align:right\">Price</th></tr></thead><tbody>' + lineItemRows + discountRow + '</tbody><tr class=\"trow\"><td>Total Investment</td><td style=\"text-align:right\">' + item.formattedTotal + '</td></tr></table><p style=\"font-size:13px;color:#64748b\">Payment Terms: ' + item.defaultPaymentTerms + '<br>Warranty: ' + item.defaultWarranty + '</p></div><div class=\"cta\"><a href=\"mailto:user@example.com?subject=Accepting Proposal ' + item.requestId + '\">Accept This Proposal</a></div><div class=\"footer\">This proposal is valid for 14 days. Questions? Reply to this email.<br>© ' + new Date().getFullYear() + ' Your Company Name. All rights reserved.</div></div></body></html>';\n\nreturn {\n json: {\n ...item,\n proposalText: proposalText,\n htmlProposal: htmlProposal,\n status: 'Proposal Generated',\n sentDate: new Date().toISOString().split('T')[0]\n }\n};"
},
"typeVersion": 2
},
{
"id": "348cbfd2-c6aa-418a-bb20-34a7a1a95fe8",
"name": "Wait - Review Buffer",
"type": "n8n-nodes-base.wait",
"position": [
3760,
688
],
"parameters": {
"amount": 3
},
"typeVersion": 1.1
},
{
"id": "8bd08989-65b9-4902-a82b-864bf0212210",
"name": "Send Proposal Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
4032,
560
],
"parameters": {
"url": "https://api.sendgrid.com/v3/mail/send",
"method": "POST",
"options": {},
"jsonBody": "={\n \"personalizations\": [{\"to\": [{\"email\": \"{{ $json.clientEmail }}\", \"name\": \"{{ $json.clientName }}\"}]}],\n \"from\": {\"email\": \"proposals@yourcompany.com\", \"name\": \"Your Company Proposals\"},\n \"reply_to\": {\"email\": \"proposals@yourcompany.com\"},\n \"subject\": \"Your Custom Proposal is Ready - {{ $json.requestId }}\",\n \"content\": [\n {\"type\": \"text/plain\", \"value\": \"Hi {{ $json.clientName }},\\n\\nYour proposal is ready.\\n\\nProposal ID: {{ $json.requestId }}\\nTotal Investment: {{ $json.formattedTotal }}\\nTimeline: {{ $json.timeline }}\\n\\nThis proposal is valid for 14 days. To accept, reply to this email.\\n\\nBest regards,\\nYour Company Team\"},\n {\"type\": \"text/html\", \"value\": \"{{ $json.htmlProposal }}\"}\n ]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "59512372-1ba6-48fb-bcfd-450667b1e248",
"name": "Log to Google Sheets Tracker",
"type": "n8n-nodes-base.httpRequest",
"position": [
4048,
736
],
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/Proposals!A1:append?valueInputOption=USER_ENTERED",
"method": "POST",
"options": {},
"jsonBody": "={\n \"values\": [[\n \"{{ $json.requestId }}\",\n \"{{ $json.sentDate }}\",\n \"{{ $json.clientName }}\",\n \"{{ $json.clientEmail }}\",\n \"{{ $json.companyName }}\",\n \"{{ $json.clientType }}\",\n \"{{ $json.industry }}\",\n \"{{ $json.templateName }}\",\n \"{{ $json.budgetTier }}\",\n \"{{ $json.formattedTotal }}\",\n \"{{ $json.timeline }}\",\n \"Sent\",\n \"{{ new Date().toISOString() }}\"\n ]]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "38b1a722-1245-4452-a954-d8210fb6e61c",
"name": "Notify Team - Slack",
"type": "n8n-nodes-base.httpRequest",
"position": [
4048,
944
],
"parameters": {
"url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"New Proposal Sent\",\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":page_facing_up: *New Proposal Sent*\\n*Client:* {{ $json.clientName }} ({{ $json.companyName }})\\n*ID:* `{{ $json.requestId }}`\\n*Template:* {{ $json.templateName }}\\n*Total:* *{{ $json.formattedTotal }}*\\n*Timeline:* {{ $json.timeline }}\\n*Email:* {{ $json.clientEmail }}\"\n }\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "bf014d73-f6d6-4a5d-bc26-7b1c755114e2",
"name": "Webhook Response - Success",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4352,
576
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, requestId: $json.requestId, message: 'Proposal sent to ' + $json.clientEmail, total: $json.formattedTotal }) }}"
},
"typeVersion": 1.1
},
{
"id": "9b2c8db0-9aa9-4426-b413-0fbc2dad4280",
"name": "Wait - Data",
"type": "n8n-nodes-base.wait",
"position": [
1424,
688
],
"parameters": {},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "9a9f6411-d529-48d6-b9c1-14f962cdc58b",
"connections": {
"Wait - Data": {
"main": [
[
{
"node": "JS - Validate and Classify",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI - Generate Proposal Copy",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Template - Agency": {
"main": [
[
{
"node": "JS - Conditional Pricing Logic",
"type": "main",
"index": 0
}
]
]
},
"Template - Startup": {
"main": [
[
{
"node": "JS - Conditional Pricing Logic",
"type": "main",
"index": 0
}
]
]
},
"Send Proposal Email": {
"main": [
[
{
"node": "Webhook Response - Success",
"type": "main",
"index": 0
}
]
]
},
"Template - Nonprofit": {
"main": [
[
{
"node": "JS - Conditional Pricing Logic",
"type": "main",
"index": 0
}
]
]
},
"Wait - Review Buffer": {
"main": [
[
{
"node": "Send Proposal Email",
"type": "main",
"index": 0
},
{
"node": "Log to Google Sheets Tracker",
"type": "main",
"index": 0
},
{
"node": "Notify Team - Slack",
"type": "main",
"index": 0
}
]
]
},
"Normalize Client Data": {
"main": [
[
{
"node": "Wait - Data",
"type": "main",
"index": 0
}
]
]
},
"Template - Enterprise": {
"main": [
[
{
"node": "JS - Conditional Pricing Logic",
"type": "main",
"index": 0
}
]
]
},
"Wait - Rate Limit Buffer": {
"main": [
[
{
"node": "AI - Generate Proposal Copy",
"type": "main",
"index": 0
}
]
]
},
"JS - Validate and Classify": {
"main": [
[
{
"node": "Filter - Valid Requests Only",
"type": "main",
"index": 0
}
]
]
},
"AI - Generate Proposal Copy": {
"main": [
[
{
"node": "JS - Format Proposal Document",
"type": "main",
"index": 0
}
]
]
},
"Switch - Client Type Router": {
"main": [
[
{
"node": "Template - Startup",
"type": "main",
"index": 0
}
],
[
{
"node": "Template - Enterprise",
"type": "main",
"index": 0
}
],
[
{
"node": "Template - Agency",
"type": "main",
"index": 0
}
],
[
{
"node": "Template - Nonprofit",
"type": "main",
"index": 0
}
],
[
{
"node": "Template - Standard Default",
"type": "main",
"index": 0
},
{
"node": "Send Validation Error Notice",
"type": "main",
"index": 0
}
]
]
},
"Template - Standard Default": {
"main": [
[
{
"node": "JS - Conditional Pricing Logic",
"type": "main",
"index": 0
}
]
]
},
"Filter - Valid Requests Only": {
"main": [
[
{
"node": "Switch - Client Type Router",
"type": "main",
"index": 0
}
]
]
},
"JS - Format Proposal Document": {
"main": [
[
{
"node": "Wait - Review Buffer",
"type": "main",
"index": 0
}
]
]
},
"JS - Conditional Pricing Logic": {
"main": [
[
{
"node": "Wait - Rate Limit Buffer",
"type": "main",
"index": 0
}
]
]
},
"Poll - Hourly Form Submissions": {
"main": [
[
{
"node": "Normalize Client Data",
"type": "main",
"index": 0
}
]
]
},
"Webhook - New Proposal Request": {
"main": [
[
{
"node": "Normalize Client Data",
"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.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts proposal requests via webhook or an hourly schedule, normalizes and validates the client data, uses OpenAI to generate a tailored proposal with pricing, then sends it via SendGrid, logs the request to Google Sheets, notifies a Slack channel, and returns a…
Source: https://n8n.io/workflows/16491/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
AI Customer Service Automation - Portfolio. Uses googleSheets, httpRequest, agent, memoryBufferWindow. Webhook trigger; 91 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.