Automation guides for small businesses. Real prices, no hype.

How to Stop Duplicate Leads in Make (Google Forms + Google Sheets)

Jhonatan Alves
How to Stop Duplicate Leads in Make (Google Forms + Google Sheets)

When the same person fills in your form twice, a basic Make scenario saves them twice. It also sends them the same welcome email twice, as if you had never talked. The fix is simple: check the email before you save anything, and give returning leads their own path.

This guide walks through a scenario I built and tested with a Google Form and a Google Sheet. New leads get a row and a welcome email. Returning leads update their existing row, raise a submission counter, and you get an email telling you they're back.

Complete Make scenario with Google Forms, Set variable, Search Rows and a router splitting new leads and returning leads
The finished scenario: one path for new leads, one for returning leads.

What you'll build

The scenario has seven modules:

  1. Google Forms > Watch Responses picks up new form submissions.
  2. Tools > Set variable cleans the email address.
  3. Google Sheets > Search Rows looks for that email in your lead list.
  4. Router splits the flow in two.
  5. New lead path: Google Sheets > Add a Row, then Gmail > Send an Email to the lead.
  6. Returning lead path: Google Sheets > Update a Row, then Gmail > Send an Email to you.

The email is the key. One email, one row, no matter how many times that person writes to you.

Before you start

You need a Google Form with three questions: Name, Email and Message.

Google Forms contact form with Name, Email and Message questions
A plain contact form is enough for this tutorial.

You also need a Google Sheet (mine is called Lead Tracker) with these headers in row 1:

email, name, message, first_contact, last_contact, submissions

Format the first_contact and last_contact columns as plain text (Format > Number > Plain text). Otherwise Google Sheets may turn the dates into its own format.

Step by step

Step 1: Watch new form responses

Create a new scenario and add Google Forms > Watch Responses. Connect your Google account and pick your form. The Limit field sets how many responses Make handles per run. A small number like 2 is fine for testing.

Make Google Forms Watch Responses module with the form selected and a limit of 2
The trigger: Make checks the form for new responses on every run.

Where are the answers? The Google Forms module doesn't deliver answers as simple "Name" or "Email" fields. Each answer sits under the question's ID, so the path looks like {{1.answers.`QUESTION_ID`.textAnswers.answers[1].value}}. Run the module once with a test response, then pick the fields from the mapping panel. Make fills in the IDs for you.

Step 2: Clean the email

Add Tools > Set variable. Name the variable clean_email and set its value to:

{{lower(trim(1.answers.`EMAIL_QUESTION_ID`.textAnswers.answers[1].value))}}
Make Tools Set variable module creating clean_email with lower and trim functions
lower() and trim() turn every version of an email into the same text.

This step matters more than it looks. trim() removes spaces at the start and end, and lower() turns everything into lowercase. Without it, "Maria@Gmail.com" and "maria@gmail.com " look like two different people, and the duplicate check fails.

Step 3: Search for the email in your sheet

Add Google Sheets > Search Rows. Select your spreadsheet and sheet, and set Table contains headers to Yes.

Make Google Sheets Search Rows module with the Lead Tracker spreadsheet and Sheet1 selected
Point the search at the same sheet where you store your leads.

In the Filter section, choose the email (A) column, the operator Text operators: Equal to, and map clean_email as the value. Set Limit to 1, since you only need to know if the email exists.

Search Rows filter comparing the email column with the clean_email variable
The search compares the stored email with the cleaned one.

Because every row is saved with clean_email, the sheet only holds clean emails. That's what makes the comparison reliable.

Step 4: Split new and returning leads

Add a Router after Search Rows. Click the line of the first route and set up a filter:

  • Label: New lead
  • Condition: Total number of bundles (from Search Rows), equal to 0
Make router filter labeled New lead with Total number of bundles equal to 0
Zero results means the email isn't in the sheet yet.

Search Rows outputs a Total number of bundles field with the number of rows it found. In my tests, the scenario kept going to the router even when the search found nothing, so this filter caught every new lead.

Step 5: Save the new lead and welcome them

On the New lead route, add Google Sheets > Add a Row and fill in the columns:

  • email: clean_email
  • name and message: the answers from the form
  • first_contact and last_contact: {{formatDate(now; "YYYY-MM-DD")}}
  • submissions: 1
Make Google Sheets Add a Row module mapping clean_email, name, message, dates and submissions set to 1
A new lead starts with both dates set to today and one submission.

Then add Gmail > Send an Email. Send it to clean_email, set Body type to Raw HTML, and use something like this:

Subject: Thanks for reaching out, {{name}}!

Hi {{name}},<br><br>
Thanks for your message. We received it and will get back to you within one business day.<br><br>
Talk soon,<br>
Your Name
Make Gmail Send an Email module with the welcome subject and Raw HTML body
Only new leads get this welcome email.

Step 6: Update the returning lead

Back at the router, set up the filter on the second route:

  • Label: Returning lead
  • Condition: Total number of bundles, Numeric operators: Greater than 0
Make router filter labeled Returning lead with Total number of bundles greater than 0
One result or more means this person already wrote to you.

Add Google Sheets > Update a Row on this route, with the same spreadsheet and sheet. Map Row number from Search Rows, then fill in only these columns:

ColumnValue
nameleave empty
messagethe new message from the form
last_contact{{formatDate(now; "YYYY-MM-DD")}}
submissions{{parseNumber(2.`5`) + 1}}

Leave email, name and first_contact empty. Update a Row doesn't touch empty fields, so the original name and the first contact date stay as they were.

Why parseNumber? Google Sheets sends cell values to Make as text, so adding 1 to the submissions column doesn't work as a plain sum. parseNumber() turns the text into a number first. The 2.`5` part is the submissions value from Search Rows (module 2). Make numbers the columns from zero, so A is 0 and F is 5.

Step 7: Get notified when a lead comes back

Add Gmail > Send an Email after Update a Row. This one goes to you, not to the lead. A returning lead often means a follow-up question or renewed interest, and it's worth a personal reply.

Subject: Returning lead: {{name}} sent a new message

A returning lead just sent a new message.<br><br>
Name: {{name}}<br>
Email: {{clean_email}}<br>
Submissions: {{parseNumber(2.`5`) + 1}}<br><br>
Message:<br>
{{message}}

Testing it

I ran three submissions with the same person, clicking Run once after each one.

Test 1: a new email. The scenario took the New lead path, added a row with one submission and sent the welcome email.

Lead Tracker sheet with one new lead row and submissions set to 1
After the first submission: one row, one submission.
Welcome email received in Gmail with the subject Thanks for reaching out, lead1
The welcome email the new lead receives.

Test 2: the same email again. This time it took the Returning lead path. No new row, and the counter went to 2.

Test 3: the same email with capital letters. Thanks to clean_email, Make still found the existing row. The counter went to 3, and the sheet still had a single row for that person.

Lead Tracker sheet still showing a single row for the lead, now with 3 submissions
Three submissions, one row. The original name was kept.

After adding the notification email, I sent one more submission to check it. This is what arrived in my inbox:

Returning lead notification email showing the lead's name, email, 4 submissions and the new message
The internal alert: who came back, how many times, and what they wrote.

What I learned building it

The form answers weren't where I expected. I assumed the Google Forms module would give me simple Name and Email fields. It doesn't. The answers are grouped by question ID, and my first mappings came out empty until I used the full path. Running the trigger once with a real response and mapping from that output saved a lot of guessing.

The counter refused to count. My first try to add 1 to the submissions column failed, because the value comes from the sheet as text. Wrapping it in parseNumber() fixed it right away.

My first returning path was silent. It updated the row and stopped there. The data was correct, but nobody knew the lead had come back. Adding the notification email turned a quiet counter into something useful for sales.

Empty fields protect your data. If you fill every column in Update a Row, you overwrite the original name and first contact date. Leave them empty and only send what changed.

Credits and scheduling

Each module that runs uses Make credits. In my test runs, one form response used between 4 and 6 credits, depending on the path. When you're done testing, click the clock on the first module, choose how often Make should check the form, and switch the scenario on. Checking every 15 minutes is fast enough for most contact forms. If you're on the free plan, a longer interval like once an hour keeps your monthly credits under control.

Using a webhook instead of the form trigger? Webhooks start the scenario the moment data arrives. If the same person submits twice within seconds, two runs could search the sheet at the same time and both find nothing. Turn on Sequential processing in the scenario settings so Make handles one submission at a time.

Common errors

ProblemCauseFix
Name or email comes out emptyGoogle Forms answers are nested under question IDsRun the trigger once and map the answer from its output: answers.`ID`.textAnswers.answers[1].value
Same person saved twiceEmail has different capital letters or extra spacesSearch and save with clean_email, never with the raw answer
Old rows aren't foundRows saved before you added clean_email still have the raw emailFix the old emails in the sheet to lowercase, with no spaces
Submissions counter doesn't increaseThe sheet sends the value as textUse parseNumber() before adding 1
Name is replaced on returnUpdate a Row has the name field mappedLeave name, email and first_contact empty in Update a Row
Wrong row updatedRow number isn't mapped from Search RowsMap Row number from the Search Rows module

Where this fits

This duplicate check works with any workflow that saves records from a form. If you're building a full lead pipeline, start with the lead capture tutorial and add this check before the save step. The same idea keeps clients from being onboarded twice in the client onboarding scenario, and from receiving the same reminder twice in the invoice reminders guide.

Want to build it yourself? You can create a free Make account here. It's an affiliate link, and it doesn't change your price.

FAQ

Can I use something other than email to detect duplicates?

Yes. Any value that is unique per person works, like a phone number or a customer ID. Clean it the same way before searching. For phone numbers, remove spaces, dashes and parentheses.

Does this work with other form tools?

Yes. Replace the Google Forms trigger with the trigger for your tool, such as Typeform, Tally or a webhook. The rest of the scenario stays the same: clean the email, search, then route.

What happens if Search Rows finds no match?

Search Rows reports zero in its Total number of bundles field. The router's New lead filter checks for that zero and sends the lead to the Add a Row path.

Can I keep every message instead of only the latest one?

Yes. In Update a Row, map the message column to the old message plus the new one, separated by a line or a date. For a long history, a second sheet with one row per message is easier to read.

Sources

Share this article:

Questions about this guide?

Tell me what you're trying to automate and I'll reply by email. Good questions often become the next guide. Get in touch →