LeadFlow360
Live demo
Case study — automation engineering

Every enquiry answered
and in your CRM
before anyone opens a laptop.

A new enquiry arrives. Within a minute it has been validated, scored, filed in the CRM, added to the team's spreadsheet, acknowledged by email and announced in Slack — with nobody copying anything by hand. This page walks through that system, running, step by step.

GET /api/v1/health
  worker  up
  d1      up
  env     production
  demo    true
60sfrom form to CRM
4tools kept in sync
13automated workflows
24/7runs unattended
0enquiries lost to an outage

Right now, a person is doing this by hand.

In most companies an enquiry lands in an inbox and waits. Someone opens it, retypes the details into the CRM, updates a spreadsheet, writes a reply, sets a reminder to chase it, and tells the team. Every step is a chance to be slow, to make a typo, or to forget entirely — and it all stops when that person is on holiday.

The cost 01

Hours spent retyping

The same details get keyed into the CRM, the spreadsheet and the reply. It is skilled people doing data entry, every day, forever.

The cost 02

Slow replies lose deals

An enquiry answered next morning competes with one answered in a minute. The gap is not effort — it is whether anyone was at a desk.

The cost 03

Follow-ups quietly stop

The first email goes out. The second and third depend on somebody remembering, so a share of every month's enquiries simply goes cold.

The cost 04

Nobody can prove what happened

"Did we ever reply to them?" turns into an archaeology exercise across an inbox, a CRM and a spreadsheet that disagree with each other.

What changes

The same work, done by software.

Nothing about the process changes for your team — the CRM, the spreadsheet, the inbox and Slack stay exactly where they are. What disappears is the person in the middle moving information between them.

When an enquiry arrivesBy hand, todayAutomated
Checking the details are usableRead it, spot the typo, guess the intentValidated on arrival, bad data rejected before it enters
Deciding who is worth calling firstA judgement call, different every timeScored on the same rules every time, with the reasoning saved
Getting it into the CRMRetyped, sometimes twice, sometimes neverCreated or updated automatically, no duplicates
Acknowledging the enquiryWhen someone gets to itWithin a minute, around the clock
Telling the teamA message, if it is rememberedPosted to Slack the moment a strong lead lands
Chasing a replyA reminder that gets snoozedScheduled follow-ups that stop by themselves once they reply
Proving what happenedSearching three toolsEvery step recorded against the enquiry

Not a new tool for your team to learn

The engine behind this is n8n, a workflow automation platform. If you have not heard of it, that is the point — nobody in your company needs to open it. It runs in the background, and your team keeps working in the CRM and the inbox they already use.

It is self-hosted, which matters for two practical reasons: your customer data never passes through a third-party automation service, and there is no per-task or per-seat fee that grows every time you get busier.

Built for your process, not a template

The rules that decide what counts as a strong enquiry are yours. Budget, timing, company size, the service they want — all of it is configuration, adjusted after you see how your real enquiries score.

The same applies to the destinations. This system talks to HubSpot, Google Sheets, Gmail and Slack because that is what the business used. Swapping any of them for Pipedrive, Outlook or Teams changes one workflow, not the architecture.

The walkthrough

Follow one enquiry through the system.

Six stages, each one shown in the software as it actually runs — not a diagram. From the moment somebody hits send to the moment the result appears on a dashboard.

01 capture source:landing|tally validated:worker accepted

A form that refuses bad data twice.

The browser validates first, then the Worker validates again from scratch — it never trusts the client. Whitespace and email are normalised, consent is mandatory, the message must be 20–2,000 characters, and a honeypot plus a submission-timing check filter bots.

UTM parameters are captured for attribution. The response never reveals the internal score or any integration ID.

The NovaGrowth lead capture form showing identity, company, role, size, service, budget and start date fields
Live capture form. The fields the scoring engine needs, and nothing it does not.
02 persist store:cloudflare-d1 idempotency:on committed

The enquiry is safe before anything else is attempted.

It is written to the database first, and only then does the automation start. If somebody clicks send twice, you get one enquiry, not two — no duplicate contact in the CRM and no duplicate email to the customer.

This is the decision that protects revenue: the enquiry is captured even if the CRM, the mail provider and the automation engine are all down. When they come back, the work resumes on its own. An outage costs you minutes of delay, never a lost customer.

The NovaGrowth landing page that hosts the capture flow
The public face. A fictional consultancy used as a realistic host — responsive from 360px and explicit about being a demonstration.
03 qualify job:PROCESS_LEAD scoring:deterministic scored

Your sales team knows who to call first.

Budget, timing, role, company size, the service requested and the quality of what they wrote produce a score and a hot, warm or cold rating — applied identically to every enquiry, at three in the morning included. The reasoning behind each score is saved, so a rating can still be explained months later instead of being an opinion nobody remembers forming.

It lives in one shared TypeScript module, exposed to n8n over an internal endpoint — visible below as an HTTP call rather than logic re-implemented in the workflow. The CRM, the dashboard and the unit tests cannot drift apart.

The LF360 10 Process Lead workflow: score, assign owner, save, record event and enqueue five jobs
LF360 10 — Process Lead. Score, assign an owner by service, persist, record the event, then enqueue five independent jobs.
04 dispatch cron:*/1 * * * * claim:leased-batch routed

A queue, not a chain of webhooks.

Every side effect is a durable job with a logical unique key, a lease, an attempt count and exponential backoff. The dispatcher runs every minute, atomically claims a leased batch so two runs cannot grab the same work, splits it and routes by job type.

Each branch is a separate subworkflow. HubSpot being down cannot delay the confirmation email or the Slack alert.

The LF360 01 Job Dispatcher workflow claiming and routing jobs to subworkflows
LF360 01 — Job Dispatcher. Cron, set worker identity, claim a batch, split, route, fan out to independently retryable subworkflows.
05 fan-out targets:hubspot|sheets|gmail|slack isolated

Four tools updated at once, none waiting on another.

The CRM record, the spreadsheet row, the confirmation email, the Slack alert and the follow-up schedule are five separate jobs. If HubSpot is having a bad morning, your customer still gets their reply on time and the CRM catches up by itself.

Emails are checked for being fully assembled before they count as sent, so a half-filled template with a placeholder where the customer's name should be is treated as a failure — not delivered and quietly embarrassing.

n8n overview listing the 13 LF360 workflows with execution statistics
The full set. LF360 00–99, numbered so execution order is obvious from the name.
06 report pii:none includes:dead-letter published

Aggregate truth, without exposing anyone.

The public dashboard reads straight from D1 and shows temperature split, meetings, replies and — deliberately — recovered jobs and dead-letter count. A pipeline that only reports successes is not telling you anything.

No personal data is exposed: no names, no emails, no raw IPs, and never the internal score.

Public dashboard with aggregate metrics including recovered and dead-letter jobs
Public dashboard. 48 synthetic leads by temperature, service and source — recovery and dead-letter counts included.
The part most demos hide

What happens on a bad day.

Every automation eventually hits a service that is down or an expired password. What separates a system you can rely on from one you cannot is whether it tells you. The red row below is a genuine failure — caught, recorded and retried, not swallowed.

↻ retry backoff:exponential attempts:bounded then:dead-letter

Failures are visible, bounded and recoverable.

A failing job backs off exponentially and is retried a bounded number of times. When it exhausts them it moves to a dead-letter path instead of looping forever — and it surfaces in the public dashboard, because a queue that never reports failures is not being honest.

A dedicated error-handler workflow sanitises the error before recording it, so no credential or personal data leaks into a log.

n8n execution history showing the dispatcher running every minute and a failed Gmail Reply Detector execution
Real execution history. The dispatcher firing every 60 seconds, and a Gmail Reply Detector error captured rather than hidden.
Engineering

Decisions worth arguing about.

Architecture

Database over automation tool

D1 accepts and acknowledges leads independently of n8n, so an automation outage degrades the system instead of losing business.

Correctness

Idempotent, leased jobs

Every job carries a logical unique key and a lease. Re-running the dispatcher or replaying a webhook cannot create duplicate contacts or emails.

Consistency

Single scoring implementation

Shared TypeScript exposed to n8n over an internal endpoint, with auditable rule breakdowns stored per lead.

Safety

Demo mode is enforced server-side

Redirects outbound email, masks recipients, flags records, rate-limits anonymised IPs and blocks irreversible actions — in the Worker, never trusted to the UI.

Privacy

Nothing sensitive is stored

No raw IPs, no tokens, no unnecessary PII. Prepared statements, field allowlists, sanitised logs, CSP and anti-framing headers.

Traceability

Twelve explicit pipeline states

From received to won, lost or archived, with every transition written to an event log.

Inventory

The thirteen workflows.

IDWorkflowResponsibility
00Error HandlerSanitises errors, records and retries without looping
01Job DispatcherClaims leased work every minute and routes it
10Process LeadScores, assigns an owner, updates D1, queues dependent work
20HubSpot UpsertUpsert with pipeline IDs discovered from the account
30Sheets SyncOperational view, keyed on a stable lead_id
40Gmail ConfirmationTemplated confirmation, fully rendered before it counts as sent
41Gmail Follow-upSequenced follow-ups with stop conditions
50Slack NotifySanitised internal alerts for hot leads and errors
60Reply DetectorDetects replies and halts the sequence
70Stage MonitorReconciles CRM stage back into the pipeline state
80Weekly ReportAggregates metrics into a scheduled report
90Retry & Dead LetterBackoff, bounded attempts and the dead-letter path
99Demo UtilitiesDemo data retention and reset
Stack & delivery

Delivered as software, not a sketch.

Every one of these gates runs before anything reaches your production systems. It is the difference between an automation that works on the day it is demoed and one you can still trust in eight months.

TypeScript React Vite Hono Cloudflare Workers Cloudflare D1 n8n HubSpot Google Sheets Gmail Slack Zod Vitest Playwright Docker pnpm
pnpm lint          # zero warnings allowed
pnpm typecheck     # tsc --noEmit, all packages
pnpm test          # 59 unit + integration
pnpm test:e2e      # Playwright
pnpm build         # production build
pnpm n8n:validate  # 13 workflow exports
pnpm security:check
Available for work

Need something like this built?

If your team is retyping the same information between tools, that work can be handed to software this month. I design and build the automation, connect it to the systems you already use, and leave you something tested that keeps running without me. Open to freelance projects and full-time roles.