Skip to main content
← All project stories

Project 01 / Demonstration build

Two lead sources, one HubSpot pipeline

Website forms and emailed leads feed one workflow. It filters junk, gets each lead to a rep in seconds, then adds company context and a score.

Category: Lead intake · Designed outcomes, not measured client results.

Web formEmailCheck + scoreHubSpot

Who this is for

Any business that gets inquiries from a website form and an email inbox (direct emails, Angi, Thumbtack, referral notices) and has someone reading them by hand.

The problem

Example client brief

“Leads come in from the website and by email. Someone reads each one, works out if it is real, looks the company up, and forwards it. When they are busy, leads sit. When a duplicate comes through, we call the same person twice.”
Three steps
  1. Web form or emailed lead arrives
  2. Checked, enriched, scored
  3. Saved in HubSpot, rep alerted in Slack, logged

Designed outcome

Every real lead reaches a rep with context. Junk never does. Nothing gets dropped silently.

What the workflow does

  1. Two intake sources. A website form posts to a webhook. Emails sent to a leads address get a Gmail label that n8n checks every minute, and Claude reads each one and pulls out the name, contact details, company, and request. That includes Angi and Thumbtack style notices where the customer is buried in a forwarded body. Anything that is not a lead is rejected and logged with the reason. Both sources end up in one shape, so the rest of the workflow exists once.
  2. Junk filter: required fields, a valid email, spam keywords, too many links, and a list of about 9,000 disposable email domains. The list refreshes itself weekly and refuses to replace itself with a bad download. Rejections go to the Google Sheets log with the reason.
  3. HubSpot contact created or updated, matched on email. A new contact also gets a deal in the New stage.
  4. Slack alert in the leads channel as soon as the lead is saved. The rep does not wait on enrichment.
  5. Enrichment runs after the alert. The workflow fetches the company's public website from the email domain, skipping Gmail, Yahoo, and other personal addresses. Claude describes the business as JSON: what they do, rough size, location, services, and a two-sentence summary. It is told to use only what the page says, and size stays unknown unless the page states it.
  6. A rules layer scores the lead from 0 to 100. The summary, size, and score are written to HubSpot custom properties and posted as a reply in the lead's Slack thread.
  7. Every lead is logged to Google Sheets as new, updated, rejected, or failed.

How the score works

The rules sit in the workflow, separate from the prompt, so changing a weight never means rewording anything for the model. Every lead starts at 20. Reading the company website adds 20. Stated company size adds 10 to 30 points, with the most for 51 to 200 people. A phone number, a company name, a message with some detail in it, and urgency words like "today" or "emergency" each add a little. A personal email domain takes 10 off.

Failing soft

Model output is parsed defensively. Code fences, prose around the JSON, truncated JSON, wrong types, or an invented size category all end in either clean fields or "could not parse" and a form-only score, never a crash. The same enrichment logic is in the repo as a standard-library Python module with 28 tests covering fetch timeouts, which errors are worth retrying (a 503 is, a 404 is not), and whatever the model sends back.

Any error nothing else catches goes to a shared error handler that posts the workflow, node, error, and execution link to the alerts channel.

Tool boundary

Everything here runs on HubSpot's free tier. Enrichment is optional to delivery: a dead website never keeps a lead from reaching a rep.

Failure handling

When it breaks

01

The same person fills out the form and then emails.

The workflow matches on email address and updates the existing contact. No second deal is opened, and the rep gets one Slack message marked “updated” instead of “new.”

02

The lead's company website is down or hangs.

The fetch gives up after 10 seconds and three tries. The lead is already in HubSpot and Slack, gets scored from what they typed, and is marked enrichment failed with the reason. An hourly job tries again, up to three times.

03

HubSpot rate-limits the write.

The workflow backs off 2, 4, then 8 seconds and tries again. After the fourth failure it posts the full lead to the alerts channel and logs it as failed, so a person enters it instead of it disappearing.