See llms.txt for all machine-readable content.

Back to Templates

Capture and deduplicate form leads with Google Sheets and email alerts

Quick overview

For small businesses that get enquiries through a website form: saves every submission to one Google Sheets lead list, merges repeat enquiries from the same person into their existing row, and emails the owner once per new lead. Works with any form that can POST to a webhook.

How it works

  1. A webhook receives each form submission as JSON or form-encoded data. It needs a name plus a valid email or phone; the email is lower-cased and the phone is reduced to its digits. Missing or invalid fields get a 400 reply listing the errors and nothing is saved. A body that isn't valid JSON is rejected by n8n with a 422.
  2. Filters spam and unauthorized posts. A filled-in hidden website field (a honeypot) gets a 200 reply and nothing is saved. If you set the optional form_secret, posts without the matching value get a 403 and nothing is saved or emailed.
  3. Queues the request so the same person is handled one request at a time. It adds a claim with its execution ID to the Intake_Locks tab, keyed on the normalized email (or the full phone number when there is no email), waits 15 seconds, reads the tab again and only continues when the first held claim for that key is its own. Later requests for the same person re-check every 5 seconds. If the claim is missing or belongs to another run, the request stops before reading Leads and the owner gets an alert email.
  4. Looks in Leads for an existing lead with the same email (any letter case) or the same full phone number. A repeat adds 1 to submissions and updates last_seen_at on the original row, keeps its details, replies duplicate and sends no alert. Numbers that differ only in their country code stay separate leads.
  5. A new person is appended to Leads and read back to confirm it was saved exactly once. Then the owner gets an alert email and the form gets created. Form text is HTML-escaped in the alert. If the alert email fails, the lead is still saved and the form still gets a 200.
  6. The claim is marked done and read back before the reply goes out. If a Google Sheets call fails, the form gets an error instead of a false success. If a save can't be confirmed, the request stops and the owner gets an alert email.

Setup

  1. Create a Google Sheet with a Leads tab and columns: lead_id, created_at, last_seen_at, name, email, phone, service, message, source, status, submissions. Add an empty Intake_Locks tab with columns: lock_key, execution_id, state. Import the workflow and connect your Google Sheets and SMTP credentials on the nodes that ask for them.
  2. In Config, set the sheet URL, sender email and owner email (for alerts). Optional: set form_secret, then send the same value from your form as a hidden form_secret field or an x-form-secret header.
  3. Activate the workflow and point your form at the Production URL of Form submission, method POST.
  4. Test before connecting your public form: post the same test lead with your own email twice. The first reply is created, with one new row and one alert email; the second is duplicate, with no new row and no second alert.

Requirements

  • Self-hosted n8n (tested on 2.43.7), core nodes only
  • A Google account (Google Sheets)
  • An SMTP mailbox
  • A form tool that can POST to a webhook, or a plain HTML form
  • No credentials are included

Customization

  • Change the alert email text in Confirm new lead
  • Turn on the shared-secret check by setting form_secret in Config
  • Change how long a request waits for an earlier one with lock_wait_seconds in Config (default 240)

Additional info

What's new: first release of this template on the n8n hub.

Testing: this version passed all 18 of its automated tests on a real n8n 2.43.7 instance on Oct 22, 2026, with a mock standing in for Google Sheets and email. The tests include four simultaneous submits with the same email, which saved one row and sent one alert. Its owner alert email was also delivered through Gmail SMTP (587/STARTTLS) to a real inbox on Oct 22, 2026. Google Sheets is tested against mock data only; a live Sheets test is pending.

Limits: requests are queued through a sheet tab, not a database lock, so duplicate protection is best effort. Don't add, delete or sort rows in Leads or Intake_Locks while requests are running. A request that stops partway can leave its claim held and block later requests for that email: check that execution, then set only its rows to done. A form tool may stop waiting before n8n replies; a retry can then return duplicate. Phone matching is best effort when the same person uses different emails. A failed alert email is not retried.

Support: [email protected]

Want more? The paid Service-Business Ops Pack for n8n by Agentica includes a lead intake mini-CRM version of this workflow, plus appointment reminders, review requests, invoice follow-ups and a weekly KPI digest: https://agenticasolutions.gumroad.com/l/ops-pack-n8n?utm_source=n8n-hub&utm_medium=template&utm_campaign=ops-pack&utm_content=f2-intake