Portfolio case study · executable demo

Lead qualification built as a system, not a chain of nodes.

An n8n reference implementation for validated intake, explainable scoring, human approval, CRM-neutral persistence, retries, audit, and safe channel handoff — with zero paid API dependencies.

3
importable workflows
6
test scenarios
0
required credentials
lead_intake / execution DEMO MODE
correlationlead_mg5qk2_1x9a2b
  1. 01
    VALIDATEDschema contract passed
    12ms
  2. 02
    NORMALIZEDcontact + source + budget
    4ms
  3. 03
    SCORED100 / HOT
    8ms
  4. 04
    NEEDS_APPROVALexecution safely paused
    WAIT

The problem

Inbound automation fails at the boundaries.

A webhook-to-CRM shortcut looks efficient until malformed payloads, duplicates, noisy alerts, approval ambiguity, or a failing downstream API appears. The workflow must preserve a lead even when the happy path stops.

  • 01Untrusted inputs reach integrations
  • 02Priority cannot be explained
  • 03Side effects happen before durable state
  • 04Failures disappear inside execution logs

Architecture

One contract. Three controlled paths.

Architecture notes
HOT≥ 70

Human approval gate

Persist, request review, pause the exact execution, then approve or reject through a separate validated webhook.

NEEDS_APPROVAL → HANDOFF_SENT
WARM40—69

Scheduled follow-up

Write the qualified lead first, acknowledge the intake, then resume after a configurable delay.

FOLLOW_UP_SCHEDULED
COLD< 40

Store without noise

Keep the record and audit trail without generating an immediate sales notification.

STORED

Deterministic intelligence

AI-style output without an AI dependency.

The Code node produces a readable summary, recommendation, and evidence list. Every point can be traced to a rule; a local LLM can be added later without becoming a critical dependency.

100/ 100HOT
Budget provided+25
Budget ≥ 300+15
Clear business task+20
Company + contacts+35
High-intent source+5
“Anna Petrova from Northwind Dental wants WhatsApp automation. Budget: 500. Recommended action: review and contact within 15 minutes.”

Failure handling

The failure path is part of the product.

↻

Bounded retries

Integration nodes retry three times with a fixed delay. Permanent failures stop retrying and move to a named terminal state.

◇

Dead-letter record

Correlation ID, failed step, error message, retry count, and the persisted lead remain available for diagnosis and replay.

≡

Append-only audit

RECEIVED through FAILED events use one portable schema: timestamp, lead ID, step, status, and message.

!

Error Trigger

Unexpected automatic-execution failures enter a separate workflow with execution and last-node context.

DOWNSTREAM 503RETRY × 3DEAD_LETTERAUDIT + REPLAY

Implementation evidence

Designed to be opened, imported, and inspected.

Test matrix

Six scenarios cover decisions and failure.

Detailed test plan
CaseScoreTierExpected terminal stateCoverage
A Complete high-intent lead100HOTHANDOFF_SENTapproval + adapters
B Qualified without budget50WARMFOLLOW_UP_SCHEDULEDwait + follow-up
C Low-context request0COLDSTOREDquiet persistence
D Missing contact + message——VALIDATION_FAILED422 + audit
E Repeated email / phone——DUPLICATEexisting lead ID
F Permanent handoff failure100HOTDEAD_LETTERretry × 3 + recovery
Repository checksnpm testworkflow shape · fixtures · mock API · links · secret scan

Production adapters

Mocks stop where credentials would begin.

The orchestration contract remains stable. Each local boundary can be replaced independently without rewriting qualification or approval logic.

PERSISTENCE

Mock CRM

File-backed lookup and upsert

→ HubSpot · Airtable · Sheets
MESSAGING

Channel adapters

Telegram, WhatsApp, email blocks

→ credentialed production nodes
OBSERVABILITY

Audit API

Portable event contract

→ database · stream · SIEM
RECOVERY

Dead letter

Replayable failure payload

→ queue · incident workflow

Source package

Inspect the decisions, not a screenshot.

Workflow exports, local infrastructure, fixtures, documentation, and verification scripts are available in the repository.