AutomationFlowsAI & RAG › Generate and Send Ai-powered Sales Quotes with Gmail, Openai and Supabase

Generate and Send Ai-powered Sales Quotes with Gmail, Openai and Supabase

ByNick @nickpuru on n8n.io

An automated quote generation system that monitors your inbox, classifies quote requests using AI, calculates intelligent pricing based on historical data, and provides a professional dashboard for review and sending quotes with one click.

Event trigger★★★★★ complexityAI-powered40 nodesGmail TriggerOpenAISupabaseSlackGmail
AI & RAG Trigger: Event Nodes: 40 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Gmail → Gmail Trigger 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": "0c011a93-d7c4-4527-9312-37fdacfa7e50",
      "name": "\ud83d\udccb Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8464,
        -944
      ],
      "parameters": {
        "width": 480,
        "height": 784,
        "content": "## \ud83c\udfaf AI QUOTE SYSTEM - COMPLETE WORKFLOW\n\n**What This Workflow Does:**\n\n1. **Two Input Sources:**\n   - Gmail inbox monitoring (every 60 seconds)\n   - Website form submissions via webhook\n\n2. **AI Processing Pipeline:**\n   - Filters emails to identify real quote requests\n   - Extracts contact info, service details, budget, timeline\n   - Classifies complexity, urgency, qualification score\n   - Searches past similar quotes for pricing intelligence\n   - Calculates 3-tier pricing (Good/Better/Best)\n   - Generates personalized draft quote email\n\n3. **Database & Notifications:**\n   - Saves everything to Supabase\n   - Sends Slack alert for high-value requests (>$10K)\n   - Status set to 'PENDING_REVIEW'\n\n4. **Replit Dashboard Integration:**\n   - Dashboard reads from Supabase\n   - Human reviews, edits pricing/email if needed\n   - Click 'Approve & Send' triggers webhook back to n8n\n   - n8n sends email via Gmail, updates status to 'SENT'\n\n**Credentials Needed:**\n- Gmail OAuth2\n- OpenAI API\n- Supabase API\n- Slack API (optional)"
      },
      "typeVersion": 1
    },
    {
      "id": "da3094f4-988c-450b-a024-c949138e5f9a",
      "name": "\ud83d\udce7 Email Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9072,
        -928
      ],
      "parameters": {
        "width": 340,
        "height": 344,
        "content": "## \ud83d\udce7 EMAIL INPUT PATH\n\nMonitors Gmail inbox every 60 seconds.\n\n**Configuration:**\n- Poll interval: Every minute\n- Filters: INBOX label only\n- Format: Resolved (full content)\n\n**Output:**\n- Subject, body (text + HTML)\n- From address\n- Message ID for tracking"
      },
      "typeVersion": 1
    },
    {
      "id": "722de022-9220-4276-adda-46ff47754cc2",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        9136,
        -544
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "labelIds": [
            "INBOX"
          ]
        },
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "971916a3-2efa-46ce-be64-ee91f1bf952f",
      "name": "\ud83c\udf10 Form Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9072,
        -144
      ],
      "parameters": {
        "width": 340,
        "height": 380,
        "content": "## \ud83c\udf10 WEBSITE FORM INPUT\n\nReceives form submissions from website.\n\n**Webhook URL:**\n`https://your-n8n.com/webhook/quote-request`\n\n**Expected Fields:**\n- name, email, phone\n- company\n- service_type\n- budget, timeline\n- message/requirements\n\n**Note:** Forms skip AI filter"
      },
      "typeVersion": 1
    },
    {
      "id": "4f35b202-4986-4d5d-a75b-85dcaaf3e589",
      "name": "Webhook - Form Submission",
      "type": "n8n-nodes-base.webhook",
      "position": [
        9136,
        256
      ],
      "parameters": {
        "path": "quote-request",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1
    },
    {
      "id": "79423dd1-8345-4ed4-b799-9c9c131d55d6",
      "name": "Respond - Form OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        9136,
        464
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"message\": \"Form received\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c2dc102a-2bd0-432a-93f5-397c851a4554",
      "name": "\ud83e\udd16 AI Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9568,
        -1008
      ],
      "parameters": {
        "width": 340,
        "height": 428,
        "content": "## \ud83e\udd16 AI EMAIL FILTER\n\n**Purpose:** Determines if email is a quote request.\n\n**Filters OUT:**\n- Newsletters & marketing\n- Spam\n- Support tickets\n- Auto-replies\n- Calendar invites\n\n**Passes THROUGH:**\n- Pricing inquiries\n- RFPs\n- Project requests\n- \"How much does X cost?\"\n\n**Threshold:** confidence >= 60"
      },
      "typeVersion": 1
    },
    {
      "id": "03c7fcea-18b0-4623-b7ec-a11703f9abaf",
      "name": "OpenAI - Email Filter",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        9616,
        -528
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o"
        },
        "options": {
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "content": "=Analyze this email and determine if it's a quote request for services.\n\n{{ $json.headers.subject }}\n**Email Body:** {{ $json.text }}\n{{ $json.headers.from }}\n\nReturn ONLY valid JSON with:\n- is_quote_request (boolean)\n- confidence (0-100)\n- detected_signals (array of keywords that indicate it's a quote)\n- reasoning (brief explanation)\n\nA quote request typically includes:\n- Request for pricing/quote/estimate\n- Service/product inquiry\n- Budget or timeline mentioned\n\nNOT a quote request:\n- Newsletters, marketing\n- Support tickets\n- Auto-replies\n- Calendar invites\n- Spam\n\nReturn ONLY the JSON object, no markdown formatting."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "bc328334-1c96-4f9b-82ba-b02044c80279",
      "name": "Parse Filter Response",
      "type": "n8n-nodes-base.code",
      "position": [
        9632,
        -320
      ],
      "parameters": {
        "jsCode": "// Parse the OpenAI response\nconst response = $input.first().json;\nlet content = response.text || response.output || response.message?.content || '';\n\n// If content is empty, try to get it from the response object directly\nif (!content && typeof response === 'object') {\n  content = JSON.stringify(response);\n}\n\ntry {\n  // Clean up any markdown formatting\n  let cleanJson = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  const parsed = JSON.parse(cleanJson);\n  \n  return {\n    json: {\n      ...parsed,\n      original_email: $('Gmail Trigger').first().json\n    }\n  };\n} catch (e) {\n  return {\n    json: {\n      is_quote_request: false,\n      confidence: 0,\n      detected_signals: [],\n      reasoning: 'Failed to parse AI response: ' + e.message,\n      original_email: $('Gmail Trigger').first().json\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "819f3511-6791-4f24-b6af-57c6a2139ea2",
      "name": "\u26a1 Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10064,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 272,
        "content": "## \u26a1 ROUTING DECISION\n\n**Email Path:**\nChecks if:\n- `is_quote_request = true`\n- `confidence >= 60`\n\n**YES** \u2192 Continue to extraction\n**NO** \u2192 Archive email\n\n**Saves API costs** by filtering spam"
      },
      "typeVersion": 1
    },
    {
      "id": "2444b9ab-d015-431b-8be8-e66242688a8a",
      "name": "IF - Is Quote Request?",
      "type": "n8n-nodes-base.if",
      "position": [
        10144,
        -544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-quote-request",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.is_quote_request }}",
              "rightValue": true
            },
            {
              "id": "condition-confidence",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.confidence }}",
              "rightValue": 60
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "296bddf1-0074-498a-837c-a7f33b9c7634",
      "name": "\ud83d\udd00 Merge Sources",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10576,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 236,
        "content": "## \ud83d\udd00 MERGE DATA SOURCES\n\nNormalizes data from both paths:\n- Email \u2192 AI filter output\n- Form \u2192 Webhook body\n\nAdds `source` field: 'email' or 'form'\n\nEnsures consistent structure."
      },
      "typeVersion": 1
    },
    {
      "id": "c86dccf6-5726-4e31-a264-11e153d5fc73",
      "name": "Merge - Both Paths",
      "type": "n8n-nodes-base.merge",
      "position": [
        10624,
        -560
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "57578f96-24af-4549-a083-22ad3a95e281",
      "name": "Normalize Data",
      "type": "n8n-nodes-base.code",
      "position": [
        10624,
        -320
      ],
      "parameters": {
        "jsCode": "// Normalize data from both sources\nconst item = $input.first().json;\n\nlet normalized = {\n  source: 'unknown',\n  raw_content: '',\n  contact_email: '',\n  timestamp: new Date().toISOString()\n};\n\n// Check if from email path (has original_email from Parse Filter)\nif (item.original_email) {\n  normalized.source = 'email';\n  normalized.raw_content = item.original_email.text || item.original_email.textPlain || item.original_email.snippet || '';\n  \n  // Extract email - original_email.from.value[0].address structure\n  if (item.original_email.from && item.original_email.from.value && item.original_email.from.value[0]) {\n    normalized.contact_email = item.original_email.from.value[0].address || '';\n  }\n  \n  normalized.subject = item.original_email.subject || '';\n  normalized.email_id = item.original_email.id || '';\n}\n// Check if from form path\nelse if (item.body || item.name) {\n  normalized.source = 'form';\n  normalized.raw_content = JSON.stringify(item.body || item);\n  normalized.contact_email = item.email || item.body?.email || '';\n  normalized.contact_name = item.name || item.body?.name || '';\n  normalized.company_name = item.company || item.body?.company || '';\n  normalized.phone = item.phone || item.body?.phone || '';\n  normalized.service_type = item.service_type || item.body?.service_type || '';\n  normalized.budget = item.budget || item.body?.budget || '';\n  normalized.timeline = item.timeline || item.body?.timeline || '';\n  normalized.message = item.message || item.body?.message || '';\n}\n\nreturn { json: normalized };"
      },
      "typeVersion": 2
    },
    {
      "id": "67a71f8d-5ff0-485d-890a-46b9d44fa4a3",
      "name": "\ud83e\udde0 Extraction",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11072,
        -992
      ],
      "parameters": {
        "width": 380,
        "height": 396,
        "content": "## \ud83e\udde0 AI EXTRACTION & CLASSIFICATION\n\n**Extracts:**\n- contact_name, company_name\n- email, phone\n- service_type (categorized)\n- budget_range, timeline\n- special_requirements\n- decision_maker (boolean)\n\n**Classifies:**\n- complexity_score (1-10)\n- urgency_level (low/med/high)\n- value_estimate (USD)\n- qualification_score (0-100)"
      },
      "typeVersion": 1
    },
    {
      "id": "66615f59-9f19-4b2b-a975-01a831e22c49",
      "name": "OpenAI - Extract & Classify",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        11136,
        -544
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o"
        },
        "options": {
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "content": "=Extract and classify this quote request.\n\n**Source:** {{ $json.source }}\n**Content:** {{ $json.raw_content }}\n**Email:** {{ $json.contact_email }}\n{{ $json.subject ? '**Subject:** ' + $json.subject : '' }}\n\nExtract these fields:\n- contact_name (string)\n- company_name (string)\n- email (string)\n- phone (string or null)\n- service_type (categorize as: freight_logistics, warehousing, last_mile, customs, consulting, other)\n- budget_range (string or null)\n- timeline (string)\n- special_requirements (string or null)\n- decision_maker (boolean)\n\nClassify:\n- complexity_score (integer 1-10)\n- urgency_level (string: low, medium, or high)\n- value_estimate (number in USD)\n- qualification_score (integer 0-100, based on: budget info +20, timeline +15, decision maker +25, clear requirements +20, company info +10, phone +10)\n\nReturn ONLY valid JSON with all fields. Use null for missing optional fields. No markdown."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "0e8e9507-2a40-4097-bf1b-5dd051a7d370",
      "name": "Parse Extraction",
      "type": "n8n-nodes-base.code",
      "position": [
        11216,
        -304
      ],
      "parameters": {
        "jsCode": "// Parse the extraction response\nconst response = $input.first().json;\nlet content = response.text || response.output || response.message?.content || '';\nconst normalized = $('Normalize Data').first().json;\n\ntry {\n  let cleanJson = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  const extracted = JSON.parse(cleanJson);\n  \n  return {\n    json: {\n      ...extracted,\n      source: normalized.source,\n      raw_content: normalized.raw_content,\n      email_id: normalized.email_id || null,\n      received_at: normalized.timestamp\n    }\n  };\n} catch (e) {\n  return {\n    json: {\n      contact_name: 'Unknown',\n      company_name: 'Unknown',\n      email: normalized.contact_email || 'user@example.com',\n      phone: null,\n      service_type: 'other',\n      budget_range: null,\n      timeline: 'Unknown',\n      special_requirements: null,\n      decision_maker: false,\n      complexity_score: 5,\n      urgency_level: 'medium',\n      value_estimate: 10000,\n      qualification_score: 30,\n      source: normalized.source,\n      raw_content: normalized.raw_content,\n      parse_error: e.message\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "2e48ad2c-9c04-41fe-8193-8cdf53926e7a",
      "name": "\ud83d\udcbe Save Initial",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11568,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 268,
        "content": "## \ud83d\udcbe SAVE INITIAL RECORD\n\nCreates record in Supabase:\n- All extracted contact info\n- Classification scores\n- Status: 'PROCESSING'\n- Timestamp\n\nReturns record ID for updates."
      },
      "typeVersion": 1
    },
    {
      "id": "3737ac06-8bb0-48d9-a743-e427ce7f2ea2",
      "name": "Prepare Insert",
      "type": "n8n-nodes-base.code",
      "position": [
        11632,
        -544
      ],
      "parameters": {
        "jsCode": "// Prepare data for Supabase insert\nconst extracted = $input.first().json;\n\nreturn {\n  json: {\n    contact_name: extracted.contact_name || 'Unknown',\n    company_name: extracted.company_name || 'Unknown',\n    email: extracted.email,\n    phone: extracted.phone,\n    service_type: extracted.service_type,\n    budget_range: extracted.budget_range,\n    timeline: extracted.timeline,\n    special_requirements: extracted.special_requirements,\n    complexity_score: extracted.complexity_score,\n    urgency_level: extracted.urgency_level,\n    value_estimate: extracted.value_estimate,\n    qualification_score: extracted.qualification_score,\n    source: extracted.source,\n    status: 'PROCESSING'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c173a985-956a-486e-b00c-5a342081003c",
      "name": "Supabase - Save Initial",
      "type": "n8n-nodes-base.supabase",
      "position": [
        11632,
        -320
      ],
      "parameters": {
        "tableId": "quote_requests",
        "dataToSend": "autoMapInputData"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f4df8101-1619-45e2-917d-41d01a6f38c7",
      "name": "\ud83d\udcca Historical",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12048,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 360,
        "content": "## \ud83d\udcca SEARCH SIMILAR QUOTES\n\n**Queries Supabase for:**\n- Same service_type\n- Status = 'WON'\n- Last 10 similar projects\n\n**Provides:**\n- Historical pricing data\n- Win rates\n- Scope comparisons\n\nImproves pricing accuracy."
      },
      "typeVersion": 1
    },
    {
      "id": "07e27243-c975-4df8-9902-56380742c834",
      "name": "Supabase - Search Similar",
      "type": "n8n-nodes-base.supabase",
      "position": [
        12128,
        -544
      ],
      "parameters": {
        "limit": 10,
        "tableId": "quote_requests",
        "operation": "getAll",
        "filterType": "string",
        "filterString": "=status=eq.WON&order=created_at.desc"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "7e1022f9-74cf-4971-a2a8-c172f7d8ab3f",
      "name": "\ud83d\udcb0 Pricing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12576,
        -944
      ],
      "parameters": {
        "width": 360,
        "height": 368,
        "content": "## \ud83d\udcb0 AI PRICING CALCULATION\n\n**Inputs:**\n- Current request details\n- Historical similar quotes\n- Business pricing rules\n\n**Calculates:**\n- good_price (basic)\n- better_price (standard)\n- best_price (premium)\n- recommended_tier\n- pricing_rationale\n\n**Formula:**\nBase \u00d7 Complexity \u00d7 Urgency"
      },
      "typeVersion": 1
    },
    {
      "id": "5507604b-dca5-4202-8a87-d3ed4bc7ebea",
      "name": "OpenAI - Calculate Pricing",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        12624,
        -544
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o"
        },
        "options": {
          "temperature": 0.3
        },
        "messages": {
          "values": [
            {
              "content": "=Calculate pricing for this quote request.\n\n**Current Request:**\n- Service: {{ $('Parse Extraction').first().json.service_type }}\n- Complexity: {{ $('Parse Extraction').first().json.complexity_score }}/10\n- Urgency: {{ $('Parse Extraction').first().json.urgency_level }}\n- Value Estimate: ${{ $('Parse Extraction').first().json.value_estimate }}\n- Timeline: {{ $('Parse Extraction').first().json.timeline }}\n\n**Similar Past Quotes:**\n{{ JSON.stringify($json) }}\n\n**Pricing Rules:**\n- Base rate: $150/hour\n- Complexity multiplier: 1.0 (score 1-3) to 2.0 (score 8-10)\n- Urgency multiplier: 1.0 (low), 1.25 (medium), 1.5 (high)\n- Minimum project: $5,000\n- Weight toward past quote pricing if available\n\n**Generate 3-tier pricing:**\n- Good: Basic service (0.8x calculated price)\n- Better: Standard + extras (1.0x) - RECOMMENDED\n- Best: Premium all-inclusive (1.4x)\n\nReturn ONLY valid JSON with no markdown:\n{\"good_price\": number, \"better_price\": number, \"best_price\": number, \"recommended_tier\": \"better\", \"pricing_rationale\": \"explanation\"}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2a11336b-eb07-42ff-b302-58dd54768dbe",
      "name": "Parse Pricing",
      "type": "n8n-nodes-base.code",
      "position": [
        12624,
        -320
      ],
      "parameters": {
        "jsCode": "// Parse pricing response\nconst response = $input.first().json;\nlet content = response.text || response.output || response.message?.content || '';\n\ntry {\n  let cleanJson = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  const pricing = JSON.parse(cleanJson);\n  \n  // Get record ID from Supabase save\n  const supabaseResult = $('Supabase - Save Initial').first().json;\n  const recordId = Array.isArray(supabaseResult) ? supabaseResult[0]?.id : supabaseResult?.id;\n  \n  return {\n    json: {\n      ...pricing,\n      extracted: $('Parse Extraction').first().json,\n      record_id: recordId\n    }\n  };\n} catch (e) {\n  const estimate = $('Parse Extraction').first().json.value_estimate || 10000;\n  const supabaseResult = $('Supabase - Save Initial').first().json;\n  const recordId = Array.isArray(supabaseResult) ? supabaseResult[0]?.id : supabaseResult?.id;\n  \n  return {\n    json: {\n      good_price: Math.round(estimate * 0.8),\n      better_price: Math.round(estimate),\n      best_price: Math.round(estimate * 1.4),\n      recommended_tier: 'better',\n      pricing_rationale: 'Fallback pricing based on estimate',\n      extracted: $('Parse Extraction').first().json,\n      record_id: recordId\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "bd97ad96-b88b-47df-b953-180a3dd925bd",
      "name": "\u2709\ufe0f Draft Email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13072,
        -944
      ],
      "parameters": {
        "width": 360,
        "height": 336,
        "content": "## \u2709\ufe0f GENERATE DRAFT EMAIL\n\n**AI writes quote including:**\n- Professional greeting\n- Acknowledgment of needs\n- 3-tier pricing options\n- What's included per tier\n- Clear CTA\n- Professional signature\n\n**Guidelines:**\n- Friendly tone\n- Under 300 words\n- Clear formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "52beadb7-b2b8-47d4-a7af-d1eccb982c78",
      "name": "OpenAI - Generate Draft",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        13136,
        -544
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o"
        },
        "options": {
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "content": "=Write a professional quote email for this request.\n\n**Contact:** {{ $json.extracted.contact_name }} from {{ $json.extracted.company_name }}\n**Email:** {{ $json.extracted.email }}\n**Service:** {{ $json.extracted.service_type }}\n**Timeline:** {{ $json.extracted.timeline }}\n**Requirements:** {{ $json.extracted.special_requirements || 'None specified' }}\n\n**Pricing Options:**\n- Good (Basic): ${{ $json.good_price }}\n- Better (Standard): ${{ $json.better_price }} \u2190 Recommended\n- Best (Premium): ${{ $json.best_price }}\n\n**Rationale:** {{ $json.pricing_rationale }}\n\n**Email Guidelines:**\n1. Professional but friendly tone\n2. Address them by first name\n3. Acknowledge their specific needs/timeline\n4. Present 3 tiers clearly with bullet points for what's included\n5. Mark the recommended option\n6. Include CTA (schedule a call or reply with questions)\n7. Keep under 300 words\n8. Sign as 'The Team'\n9. Don't use em dashes \n\nWrite ONLY the email body text. No subject line, no JSON, no markdown formatting."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "91811f78-e3f2-4156-ad0a-f731e407be08",
      "name": "Prepare Update",
      "type": "n8n-nodes-base.code",
      "position": [
        13136,
        -320
      ],
      "parameters": {
        "jsCode": "// Extract draft email and prepare update\nconst response = $input.first().json;\nconst draftEmail = response.text || response.output || response.message?.content || '';\nconst pricing = $('Parse Pricing').first().json;\n\nreturn {\n  json: {\n    id: pricing.record_id,\n    good_price: pricing.good_price,\n    better_price: pricing.better_price,\n    best_price: pricing.best_price,\n    draft_email: draftEmail,\n    priority_score: pricing.extracted.qualification_score,\n    status: 'PENDING_REVIEW',\n    extracted: pricing.extracted\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b04bcea0-0bc2-4758-98a5-58f36101b57e",
      "name": "\ud83d\udd04 Update DB",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13568,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 292,
        "content": "## \ud83d\udd04 UPDATE DATABASE\n\n**Adds to record:**\n- good_price\n- better_price\n- best_price\n- draft_email content\n- priority_score\n- Status: 'PENDING_REVIEW'\n\n**Now ready for:**\nHuman review in dashboard"
      },
      "typeVersion": 1
    },
    {
      "id": "28d1e3a5-82a4-4819-a7b4-e94fcd510161",
      "name": "Supabase - Update Complete",
      "type": "n8n-nodes-base.supabase",
      "position": [
        13632,
        -544
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $json.id }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "quote_requests",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "good_price",
              "fieldValue": "={{ $json.good_price }}"
            },
            {
              "fieldId": "better_price",
              "fieldValue": "={{ $json.better_price }}"
            },
            {
              "fieldId": "best_price",
              "fieldValue": "={{ $json.best_price }}"
            },
            {
              "fieldId": "draft_email",
              "fieldValue": "={{ $json.draft_email }}"
            },
            {
              "fieldId": "priority_score",
              "fieldValue": "={{ $json.priority_score }}"
            },
            {
              "fieldId": "status",
              "fieldValue": "PENDING_REVIEW"
            }
          ]
        },
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c5e81a95-b31a-46cf-b8b5-138bb4d94bee",
      "name": "\ud83d\udd14 Slack Alert",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        14064,
        -944
      ],
      "parameters": {
        "width": 340,
        "height": 332,
        "content": "## \ud83d\udd14 SLACK NOTIFICATION\n\n**Triggers when:**\nvalue_estimate >= $10,000\n\n**Alert includes:**\n- Company name\n- Contact name\n- Estimated value\n- Service type\n- Urgency level\n- Dashboard link\n\n**Optional:** Replace with email"
      },
      "typeVersion": 1
    },
    {
      "id": "8594da05-9ee9-4100-aefd-227ad43550a1",
      "name": "IF - High Value?",
      "type": "n8n-nodes-base.if",
      "position": [
        14128,
        -544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "high-value-check",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $('Prepare Update').first().json.extracted.value_estimate }}",
              "rightValue": 10000
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "22ce20d4-d45d-4c10-97fe-f7a4fe180cf5",
      "name": "Slack - High Value Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        14128,
        -320
      ],
      "parameters": {
        "text": "=\ud83d\udd25 *High-Value Quote Request!*\n\n*Company:* {{ $('Prepare Update').first().json.extracted.company_name }}\n*Contact:* {{ $('Prepare Update').first().json.extracted.contact_name }}\n*Email:* {{ $('Prepare Update').first().json.extracted.email }}\n*Estimated Value:* ${{ $('Prepare Update').first().json.extracted.value_estimate }}\n*Service:* {{ $('Prepare Update').first().json.extracted.service_type }}\n*Urgency:* {{ $('Prepare Update').first().json.extracted.urgency_level }}\n\n<https://your-replit-dashboard.com|View in Dashboard>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C08MU2RNGTU",
          "cachedResultName": "ai-assistant"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8c2d5fca-0898-45ba-b909-246bebda356e",
      "name": "\ud83d\udce4 Send Webhook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10464,
        976
      ],
      "parameters": {
        "width": 380,
        "height": 404,
        "content": "## \ud83d\udce4 SEND QUOTE WEBHOOK\n\n**Triggered by Replit** when user clicks\n'Approve & Send'\n\n**Receives:**\n- quote_id\n- recipient_email\n- company_name\n- service_type\n- final_email_body\n\n**Actions:**\n1. Send email via Gmail\n2. Update status to 'SENT'\n3. Return success response"
      },
      "typeVersion": 1
    },
    {
      "id": "e8e0812c-1835-43e3-8c37-75fa139f4d99",
      "name": "Webhook - Send Quote",
      "type": "n8n-nodes-base.webhook",
      "position": [
        10576,
        1424
      ],
      "parameters": {
        "path": "send-quote",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1
    },
    {
      "id": "f1d4d017-4f4b-4180-aeb9-6e901a1fa7c1",
      "name": "Gmail - Send Quote",
      "type": "n8n-nodes-base.gmail",
      "position": [
        11072,
        1424
      ],
      "parameters": {
        "sendTo": "={{ $json.body.recipient_email }}",
        "message": "={{ $json.body.final_email_body }}",
        "options": {},
        "subject": "=Quote for {{ $json.body.company_name }} - {{ $json.body.service_type }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "9f2332b9-f09e-429a-8309-3e7975e28224",
      "name": "Set Sent Status",
      "type": "n8n-nodes-base.code",
      "position": [
        11568,
        1424
      ],
      "parameters": {
        "jsCode": "return {\n  json: {\n    id: $('Webhook - Send Quote').first().json.body.quote_id,\n    status: 'SENT',\n    sent_at: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "5a3ee42b-d3dd-4f35-ac2a-9bfc8ac22605",
      "name": "Supabase - Mark Sent",
      "type": "n8n-nodes-base.supabase",
      "position": [
        12064,
        1424
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $json.id }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "quote_requests",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "status",
              "fieldValue": "={{ $json.status }}"
            },
            {
              "fieldId": "sent_at",
              "fieldValue": "={{ $json.sent_at }}"
            }
          ]
        },
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "707851ff-ddf3-4884-9e95-a3347177ace3",
      "name": "Respond - Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        12576,
        1424
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"quote_id\": \"{{ $('Webhook - Send Quote').first().json.body.quote_id }}\",\n  \"status\": \"SENT\",\n  \"message\": \"Quote sent to {{ $('Webhook - Send Quote').first().json.body.recipient_email }}\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6e01b961-8b7d-4a43-af9c-13c17e15b15d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9072,
        -352
      ],
      "parameters": {
        "width": 336,
        "height": 192,
        "content": "@[youtube](sAGuFV60Big)"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "OpenAI - Email Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Pricing": {
      "main": [
        [
          {
            "node": "OpenAI - Generate Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Data": {
      "main": [
        [
          {
            "node": "OpenAI - Extract & Classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Insert": {
      "main": [
        [
          {
            "node": "Supabase - Save Initial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Update": {
      "main": [
        [
          {
            "node": "Supabase - Update Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Sent Status": {
      "main": [
        [
          {
            "node": "Supabase - Mark Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - High Value?": {
      "main": [
        [
          {
            "node": "Slack - High Value Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Extraction": {
      "main": [
        [
          {
            "node": "Prepare Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Send Quote": {
      "main": [
        [
          {
            "node": "Set Sent Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge - Both Paths": {
      "main": [
        [
          {
            "node": "Normalize Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase - Mark Sent": {
      "main": [
        [
          {
            "node": "Respond - Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Send Quote": {
      "main": [
        [
          {
            "node": "Gmail - Send Quote",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Email Filter": {
      "main": [
        [
          {
            "node": "Parse Filter Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Filter Response": {
      "main": [
        [
          {
            "node": "IF - Is Quote Request?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - Is Quote Request?": {
      "main": [
        [
          {
            "node": "Merge - Both Paths",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "OpenAI - Generate Draft": {
      "main": [
        [
          {
            "node": "Prepare Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase - Save Initial": {
      "main": [
        [
          {
            "node": "Supabase - Search Similar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase - Search Similar": {
      "main": [
        [
          {
            "node": "OpenAI - Calculate Pricing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Form Submission": {
      "main": [
        [
          {
            "node": "Merge - Both Paths",
            "type": "main",
            "index": 1
          },
          {
            "node": "Respond - Form OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Calculate Pricing": {
      "main": [
        [
          {
            "node": "Parse Pricing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase - Update Complete": {
      "main": [
        [
          {
            "node": "IF - High Value?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Extract & Classify": {
      "main": [
        [
          {
            "node": "Parse Extraction",
            "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

An automated quote generation system that monitors your inbox, classifies quote requests using AI, calculates intelligent pricing based on historical data, and provides a professional dashboard for review and sending quotes with one click.

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

• The OpenAI API is used for automatic email classification, which incurs a small per-request cost. See OpenAI Pricing for up-to-date info. • You can easily expand the categories or connect more Slack

Gmail Trigger, Slack, OpenAI +2
AI & RAG

Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:

Gmail Trigger, Google Sheets, OpenAI +3
AI & RAG

Overview

Gmail Trigger, Google Drive, OpenAI +4
AI & RAG

A fully automated, AI-powered email assistant built in n8n that reads incoming emails, understands their intent and sentiment, classifies them by category, drafts intelligent context-aware replies, an

Gmail Trigger, Slack, Gmail +2
AI & RAG

Small teams, solo operators, and security-conscious individuals who receive email attachments from external senders. Useful for freelancers, agencies, HR teams, and anyone handling CVs, invoices, or d

Gmail Trigger, HTTP Request, OpenAI +4