Multi‑Workflow Lead Intake, Qualification & Routing System (GitHub Pages → n8n → Airtable → Telegram)


Hey everyone — wanted to share a project I’ve been working on that might be useful for anyone handling inbound leads, waitlists, or form → CRM pipelines.

This is a multi‑workflow architecture built entirely in n8n, designed to be modular, fast, and production‑safe. It handles intake, validation, enrichment, scoring, CRM sync, and notifications — All of this runs without a custom backend server — n8n handles the entire backend logic through modular workflows.

:puzzle_piece: High‑Level Architecture

GitHub Pages Form
      │  POST JSON
      ▼
WF‑1  Lead Intake & Validation
      — Field validation
      — Sanitization
      — Duplicate detection
      — 200 response immediately
      │
      ▼
WF‑2  Enrichment & Lead Scoring
      — Brandfetch domain enrichment
      — Abstract API email reputation
      — Score 0–100
      — Label Hot/Warm/Cold
      │
      ▼
WF‑3  CRM Sync & Notifications
      — Create-only Airtable record
      — Score-based routing
      — Telegram alerts
      │
      ▼
WF‑E  Global Error Handler
      — Catches failures across all workflows
      — Telegram error alerts

Each workflow has a single responsibility and communicates via webhook, so they can be updated or paused independently.

:shield: Client-Side Spam Protection

The public form includes:

  • HTML5 validation
  • Custom JS validation
  • Disposable domain blocking
  • Fake domain blocking
  • Invalid TLD blocking
  • Keyboard‑smash detection
  • Honeypot field

Invalid submissions never reach n8n.

:package: GitHub Repo (Form Only)

The GitHub repo contains the public-facing form, validation logic, and documentation:

:backhand_index_pointing_right: GitHub - NelsonEm9/inbound-lead-intelligence-form: Minimal, high-converting lead capture form built with vanilla HTML, CSS, and JavaScript. Submits to an n8n webhook pipeline for automated scoring, CRM sync, and instant notifications. Includes honeypot spam protection, full UTM attribution, client-side validation, and duplicate email handling. · GitHub

(This repo does not include the workflows themselves — just the form and docs.)

:shield: Production‑Ready Tips (What Made This Reliable)

A few things that made this architecture stable in production:

1. Multi‑workflow separation

Splitting the pipeline into 3 async workflows made debugging and iteration dramatically easier. No single workflow has to do everything.

2. Create‑only CRM writes

WF‑3 only creates new Airtable records — no updates.
This avoids race conditions and keeps the CRM logic simple.

3. Early deduplication

WF‑1 checks for duplicates before any enrichment or scoring.
This saves API calls and keeps downstream workflows clean.

4. Global error handler

WF‑E catches failures from any workflow and sends a Telegram alert with the execution ID.
No silent failures.

5. Client-side validation

The GitHub Pages form blocks:

  • disposable domains

  • fake domains

  • invalid TLDs

  • keyboard‑smash names

  • honeypot bots

Bad data never reaches n8n.

6. Async webhook chaining

Each workflow calls the next via webhook, which keeps the system responsive and modular.

:speech_balloon: Happy to Answer Questions

If anyone is curious about the architecture, scoring model, or how the workflows communicate, I’m happy to answer questions or talk through the design decisions.

2 Likes

Hi @NE_automation

This is a highly sophisticated, “enterprise-lite” architecture. By decoupling the intake from the processing, you’ve solved the most common failure point in no-code lead gen: the webhook timeout. Most users make the mistake of putting enrichment and CRM writes in a single workflow, which leads to frontend timeouts and a poor user experience.

Strengths

The decision to have WF-1 return a 200 Accepted immediately while pushing the heavy lifting to WF-2 and WF-3 is the gold standard for production pipelines. It ensures the user isn’t staring at a loading spinner while Brandfetch and Abstract API are responding.

By splitting the logic into four distinct workflows, you’ve made the system maintainable. If you decide to switch from Airtable to Salesforce or from Telegram to Slack, you only touch one workflow (WF-3) without risking the validation logic in WF-1.

Your spam protection is comprehensive. Implementing honeypots and TLD blocking before the data hits n8n saves you from wasting API credits on Brandfetch/Abstract and prevents your Airtable from becoming a junk drawer.

The inclusion of a dedicated error workflow is what separates a “hobby project” from a “production system.” In n8n, silent failures are common; having a centralized Telegram alert for execution IDs allows for rapid debugging.

Potential Weaknesses

You are using async HTTP calls to move data between WF-1 → WF-2 → WF-3. If WF-2 is down or crashes during the transition, the lead is lost. Since WF-1 already told the user “Success,” there is no way to recover that lead unless you have a “Dead Letter Queue” or a temporary log of all incoming requests. You have an error handler (WF-E), but if the trigger of the next workflow fails, WF-E might not even know it happened.

Your scoring model relies on “character length” for use-case quality. A bot or a low-quality lead can simply paste a long, irrelevant paragraph to trigger a “Hot” lead score.

WF-3 is strictly create-only to avoid race conditions. While this simplifies logic, it means you have no “Lead Lifecycle” management. If a lead returns to the form and updates their info, you’ll either block them (via WF-1 dedupe) or create a duplicate. You lose the ability to track a lead’s evolution over time.

Overall, I would still say it is a good attempt

2 Likes

Thanks for taking the time to write such a detailed review - I really appreciate the depth of the feedback. A few clarifications on the potential weaknesses you mentioned, since some of the behavior in n8n isn’t always obvious at first glance:

1. On the async handoff and “lost leads”

The main point I want to clarify is how n8n handles the Respond to Webhook node, because this is easy to misunderstand:

Returning 200 to the frontend only ends the HTTP request, it does NOT end WF‑1.
The workflow continues running after the response is sent.

So in WF‑1:

  • The user gets a fast 200 response for UX
  • But WF‑1 keeps executing in the background
  • It still sends the HTTP call to WF‑2
  • It still waits for WF‑2’s response
  • And if WF‑2 is unreachable or returns a non‑200, the HTTP Request node fails
  • Which means WF‑1 fails, and WF‑E catches it

So the async handoff is intentional for performance, but it’s not a blind fire‑and‑forget.
WF‑E still catches failures from WF‑1, WF‑2, or WF‑3.

WF‑1 also logs every accepted payload before forwarding it, so even in the unlikely case that WF‑2 never starts, the lead isn’t lost and can be replayed.

2. On use‑case length being “gamed”

Use‑case length is only one of four scoring pillars.
A long paragraph alone can’t produce a Hot score because:

  • seniority
  • company size
  • and completeness

…carry most of the weight.

The form also enforces a max length on the textarea, so the “paste a giant essay” scenario isn’t possible.

3. On create‑only CRM writes

Correct because WF‑3 is intentionally create‑only.
Lifecycle management happens in the CRM, not in the intake pipeline.

This keeps the workflow deterministic and avoids race conditions.

4. On the lack of a dead‑letter queue

WF‑1’s logging + WF‑E’s global error handling effectively IS the dead‑letter queue.

Every accepted payload is stored before enrichment, and any downstream failure triggers a Telegram alert with the execution ID. Nothing disappears silently.

Thanks again for the thoughtful review, I am always happy to discuss workflow patterns.

1 Like

Clean architecture and the clarification on how Respond to Webhook works is spot-on - that’s a common source of confusion for people building multi-step flows where they want a fast 200 without blocking the caller.

One small pattern that pairs well with WF-1’s pre-forwarding log: store a unique submission_id (e.g. a UUID generated in WF-1 using {{ $now.toMillis() }}-{{ $randomString }}) alongside the payload in Airtable. This makes replaying failed leads by ID much simpler and lets you deduplicate resubmissions from the form without checking the full payload each time.