AutomationFlowsEmail & Gmail › AI Qualify Leads & Book via Gmail

AI Qualify Leads & Book via Gmail

Original n8n title: AI Qualified Booking

AI Qualified Booking. Uses lmChatAnthropic, chainLlm, gmail, googleSheets. Webhook trigger; 12 nodes.

Webhook trigger★★★★☆ complexityAI-powered12 nodesAnthropic ChatChain LlmGmailGoogle Sheets
Email & Gmail Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Chainllm → Gmail 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
{
  "name": "AI Qualified Booking",
  "tags": [
    {
      "name": "NTF-Playbook"
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "nodes": [
    {
      "id": "sticky-readme",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -520
      ],
      "parameters": {
        "content": "## AI Qualified Booking\n\n**Trigger:** Webhook, intake form submission\n\n**Flow:**\n1. Prospect submits intake form (Tally/Typeform/Google Forms)\n2. Claude scores against ICP criteria: Qualified / Borderline / Not a Fit\n3. Qualified, gets Calendly link automatically via email\n4. Borderline, routed to manual review for follow-up question\n5. Not a Fit, receives polite decline email with alternative resources\n6. All leads logged to Google Sheets\n\n**Setup:**\n- Set CALENDLY_LINK and YOUR_NAME in the Config node\n- Connect Gmail credential for sending emails\n- Edit Claude ICP criteria inside the Qualify Against ICP node\n- Connect Google Sheets credential and set the spreadsheet ID\n- The webhook URL becomes your form's submission endpoint",
        "height": 380,
        "width": 560,
        "color": 6
      }
    },
    {
      "id": "webhook-trigger",
      "name": "Intake Form Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "booking-intake",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "config",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "calendly_link",
              "name": "calendly_link",
              "value": "YOUR_CALENDLY_LINK",
              "type": "string"
            },
            {
              "id": "your_name",
              "name": "your_name",
              "value": "YOUR_NAME",
              "type": "string"
            },
            {
              "id": "name",
              "name": "name",
              "value": "={{ $json.body.name || '' }}",
              "type": "string"
            },
            {
              "id": "email",
              "name": "email",
              "value": "={{ $json.body.email || '' }}",
              "type": "string"
            },
            {
              "id": "company",
              "name": "company",
              "value": "={{ $json.body.company || '' }}",
              "type": "string"
            },
            {
              "id": "role",
              "name": "role",
              "value": "={{ $json.body.role || '' }}",
              "type": "string"
            },
            {
              "id": "challenge",
              "name": "challenge",
              "value": "={{ $json.body.challenge || $json.body.message || '' }}",
              "type": "string"
            },
            {
              "id": "budget",
              "name": "budget",
              "value": "={{ $json.body.budget || '' }}",
              "type": "string"
            },
            {
              "id": "timeline",
              "name": "timeline",
              "value": "={{ $json.body.timeline || '' }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "claude-llm",
      "name": "Claude Qualification Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        440,
        0
      ],
      "parameters": {
        "model": "claude-sonnet-4-5",
        "options": {
          "maxTokens": 600
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "qualify-chain",
      "name": "Qualify Against ICP",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.4,
      "position": [
        440,
        0
      ],
      "parameters": {
        "promptType": "define",
        "text": "=You are a sales qualification assistant. Score this prospect against the ICP criteria below.\n\nReturn JSON with:\n- score: \"Qualified\" | \"Borderline\" | \"Not a Fit\"\n- reasoning: one sentence explaining the score\n- confidence: 0-100 integer\n\nICP CRITERIA (edit these):\n- Ideal: founder or senior ops/marketing role, company 10-200 people, has a specific automation or efficiency problem, timeline within 90 days\n- Borderline: right role but vague problem, or right problem but no timeline\n- Not a Fit: agencies looking for white-label services, students, purely technical developers building their own tools, no budget indicated\n\nProspect:\nName: {{ $json.name }}\nEmail: {{ $json.email }}\nCompany: {{ $json.company }}\nRole: {{ $json.role }}\nChallenge: {{ $json.challenge }}\nBudget: {{ $json.budget }}\nTimeline: {{ $json.timeline }}\n\nReturn only valid JSON. No markdown fencing."
      }
    },
    {
      "id": "parse-score",
      "name": "Parse Score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.text || '{}';\nlet parsed = {};\ntry {\n  parsed = JSON.parse(raw.replace(/```json|```/g, '').trim());\n} catch(e) {\n  parsed = { score: 'Borderline', reasoning: 'Parse error', confidence: 50 };\n}\nconst config = $('Config').first().json;\nreturn [{ json: { ...config, ...parsed, submitted_at: new Date().toISOString() } }];"
      }
    },
    {
      "id": "route-by-score",
      "name": "Route by Score",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        880,
        0
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{ $json.score }}",
                    "rightValue": "Qualified",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "qualified"
            },
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{ $json.score }}",
                    "rightValue": "Borderline",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "borderline"
            },
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{ $json.score }}",
                    "rightValue": "Not a Fit",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "not_a_fit"
            }
          ]
        }
      }
    },
    {
      "id": "email-qualified",
      "name": "Email Send Calendar Link",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1100,
        -240
      ],
      "parameters": {
        "operation": "send",
        "sendTo": "={{ $json.email }}",
        "subject": "=Let's talk, book a time that works for you",
        "message": "=Hi {{ $json.name }},\n\nThanks for reaching out. Based on what you shared, I think there's a strong fit and I'd love to learn more.\n\nBook a time that works for you: {{ $json.calendly_link }}\n\nLooking forward to it.\n\n{{ $json.your_name }}",
        "options": {}
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "email-borderline",
      "name": "Email Review Notification",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1100,
        0
      ],
      "parameters": {
        "operation": "send",
        "sendTo": "={{ $json.email }}",
        "subject": "=Thanks for reaching out, a quick question",
        "message": "=Hi {{ $json.name }},\n\nThanks for getting in touch. I'd love to understand your situation better before we schedule time.\n\nCould you share a bit more about [YOUR SPECIFIC QUESTION]? Just a few sentences is enough.\n\nI'll follow up once I've had a chance to review.\n\n{{ $json.your_name }}",
        "options": {}
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "email-not-a-fit",
      "name": "Email Polite Decline",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1100,
        240
      ],
      "parameters": {
        "operation": "send",
        "sendTo": "={{ $json.email }}",
        "subject": "=Thanks for reaching out",
        "message": "=Hi {{ $json.name }},\n\nThank you for reaching out and for taking the time to share your situation.\n\nAfter reviewing your intake, I don't think I'm the right fit for what you need right now, but I don't want to leave you without options.\n\nYou might find value in: [ALTERNATIVE RESOURCE OR REFERRAL].\n\nWishing you the best.\n\n{{ $json.your_name }}",
        "options": {}
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "log-all",
      "name": "Log All to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1320,
        0
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Intake",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Submitted At": "={{ $json.submitted_at }}",
            "Name": "={{ $json.name }}",
            "Email": "={{ $json.email }}",
            "Company": "={{ $json.company }}",
            "Role": "={{ $json.role }}",
            "Score": "={{ $json.score }}",
            "Confidence": "={{ $json.confidence }}",
            "Reasoning": "={{ $json.reasoning }}"
          }
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "webhook-response",
      "name": "Webhook Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1320,
        -240
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ received: true }) }}"
      }
    }
  ],
  "connections": {
    "Intake Form Webhook": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Qualify Against ICP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Qualification Model": {
      "ai_languageModel": [
        [
          {
            "node": "Qualify Against ICP",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Qualify Against ICP": {
      "main": [
        [
          {
            "node": "Parse Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Score": {
      "main": [
        [
          {
            "node": "Route by Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Score": {
      "main": [
        [
          {
            "node": "Email Send Calendar Link",
            "type": "main",
            "index": 0
          },
          {
            "node": "Webhook Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Review Notification",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Polite Decline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Send Calendar Link": {
      "main": [
        [
          {
            "node": "Log All to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Review Notification": {
      "main": [
        [
          {
            "node": "Log All to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Polite Decline": {
      "main": [
        [
          {
            "node": "Log All to Sheets",
            "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

How this works

Streamline your lead qualification process with this workflow, which automatically assesses incoming form submissions using AI to determine if they match your ideal customer profile, saving time on manual reviews and ensuring only high-potential prospects advance. It's designed for sales teams or small businesses handling inbound enquiries via web forms, particularly those relying on Google Sheets for data management and Gmail for communications. The key step involves the Claude Qualification Model from Anthropic, which analyses responses against your criteria, followed by a scoring chain that routes qualified leads to send a calendar booking link directly.

Use this workflow when you receive a steady stream of form submissions that need quick vetting before scheduling calls, such as for service-based consultancies or SaaS demos. Avoid it for very high-volume leads exceeding thousands daily, where more robust CRM tools like HubSpot might be necessary, or if your qualification doesn't benefit from AI nuance. Common variations include swapping Gmail for Outlook emails or adding a Slack notification for low-scoring leads to nurture separately.

About this workflow

AI Qualified Booking. Uses lmChatAnthropic, chainLlm, gmail, googleSheets. Webhook trigger; 12 nodes.

Source: https://github.com/MinaSaad1/n8n-ai-qualified-booking/blob/main/workflows/01-ai-qualified-booking.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

✨🔪 Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, limit, stickyNote. Webhook trigger; 54 nodes.

Gmail, Gmail Trigger, HTTP Request +6
Email & Gmail

WordPress Contact Form (CF7) Responses and Classification. Uses lmChatGoogleGemini, textClassifier, gmail, chainLlm. Webhook trigger; 24 nodes.

Google Gemini Chat, Text Classifier, Gmail +3
Email & Gmail

This workflow optimizes the management of inquiries received through a contact form (Contact Form 7 - CF7 Plugin) on a WordPress site, automating the process of classification, response drafting, and

Google Gemini Chat, Text Classifier, Gmail +3
Email & Gmail

This template is ideal for HR teams, startup founders, operations leads, remote-first companies, and freelancers managing onboarding manually or across multiple tools.

Google Sheets Trigger, Jira, HubSpot Trigger +7
Email & Gmail

This template automates the complete hiring pipeline for digital agencies managing applications across multiple job roles. When a candidate submits a Google Form with their CV, the system scores it wi

OpenRouter Chat, Output Parser Structured, Google Sheets +6