Prospecting builds an outbound sales list from Google's business listings: search for a kind of business in a place, narrow the results down with automatic checks, then import the ones worth talking to straight into the CRM as companies and contacts.
It answers the question a keyword search can't: what have I actually covered? A single Google search returns at most 60 businesses and never tells you which ones it left out, so a "coverage sweep" divides an area into sections, searches each one, and divides again wherever a section comes back full.
The problem
Building a list of local businesses by hand is the slowest part of outbound sales, and searching by keyword makes it impossible to know when you are finished. "Electrician Houston" returns 60 results out of thousands, silently. Search "electrician Katy" next and you get an overlapping set with no way to tell the new names from the ones you have already called — and any status, note or email address you recorded gets lost the moment you re-run the search.
The fix
A feature module under app/Features/Prospecting/ with three parts:
- Find — keyword searches and whole-area coverage sweeps against the Google Places API.
- Qualify — free filters over what Google already returned, plus automatic audits of each business's own website.
- Import — the keepers become CRM companies and contacts, tagged so an automation can pick them up.
Every business is stored once, keyed on its Google place ID. Finding the same business again refreshes what Google says about it and touches nothing you have done to it — the status, the email you tracked down, the notes, the CRM links all survive. That is what makes overlapping searches safe, and overlapping searches are unavoidable.
Setup
Prospecting needs a Google Places API key on a Google Cloud project with billing enabled and the Places API (New) switched on. Paste it into the prompt at the top of the Prospecting page, or under SEO Scanner → Settings — it is the same key (seo_scanner.places_api_key), one Google project and one bill. Everything except searching works without a key: you can still filter, qualify, edit and import whatever is already on the list.
Searching costs money per request. See What it costs.
Finding businesses
Keyword search
Type what you are looking for ("electrician") and where ("Dallas TX"). Google returns up to 60 businesses with name, address, phone, website, rating, review count and category. Each result is added to the list or matched to a business already on it; the notification says how many were found, how many were new, and how many were already known.
The Recent strip under the search box shows the last five searches as new of total — the number that tells you whether an area is worth searching again. Two new out of sixty means you have that area.
Coverage sweeps
A sweep searches a whole area to completion. Give it a category, a place name ("Texas", "Tarrant County", "Dallas"), and a search budget, and it works through the area in the background — you can leave the page.
How it works, and why it is built this way:
- The area is turned into a rectangle from Google's own bounding box for that place, and searched as one section.
- A section that comes back under the limit is finished. Everything Google lists in that box is now on your list.
- A section that comes back full has truncated, and there is no way to know what it dropped — so it is quartered and each quarter is searched in turn.
- That repeats until every section comes back under the limit.
The effect is that the sweep tunes itself to where the businesses are. Empty countryside resolves in a single search; a dense metro keeps subdividing until it stops overflowing. Money lands where the businesses are rather than being spread evenly over a map that is mostly farmland.
Rectangles rather than towns or ZIP codes is a deliberate choice. Town names overlap ambiguously — a business in Irving may or may not surface under a Dallas search, so coverage can never be proven. ZIP codes are mostly empty countryside: Texas has around 1,900 of them and you would pay for all 1,900 to find that most hold one electrician. Rectangles tile an area exactly, so every business is in precisely one section and "have we looked here" has an answer.
You never have to think in rectangles, though. The panel reports percent swept, businesses found, sections still to search, and searches spent — and names the places involved using the towns Google already returned with the results.
When a sweep stops
- Finished — every section came back under the limit. The list is complete for that area, to the detail limit below.
- Budget spent — the sweep reached its search ceiling and paused. Nothing is lost and nothing is re-searched: type a higher budget, press Carry on, and it continues from exactly where it stopped.
- Too many businesses to list them all in: Houston, Dallas — a warning naming sections that were still full at the finest detail the sweep will go to. A city block can genuinely hold sixty businesses, so subdivision has a floor. Those areas are reported by name rather than quietly dropped, so you know where the list under-reports.
- Areas that could not be searched — a section whose search failed (an expired key, a quota, a network blip) is recorded with Google's own error and a Try again button. Its ground is not counted as swept: an area we could not search is not an area we searched and found nothing in.
Sweeps run a few sections at a time on the site's background schedule, so a large area fills in over hours. Search 5 now does a burst immediately. From the terminal, php artisan prospecting:sweep --limit=200 drains as much as you like in one go.
Removing a sweep removes its record of where it looked. The businesses it found stay on the list — they belong to the list, not to the sweep.
Qualifying the list
Filters
All of these read fields Google already returned, so filtering costs nothing extra:
- Status — New / Qualified / Rejected / Imported, with counts.
- Website — has one, or none listed. A business with no website at all is a different pitch from one with a bad website.
- Minimum reviews — a proxy for whether the business is actually trading.
- Find — free text over name and address.
Automatic audits
Audits check each business's own website, and run a few at a time in the background. The Audit filter narrows the list to those that failed a given check — a failure is the pitch, so it is the one that stands out in the list.
- PageSpeed — Google's Lighthouse performance score out of 100, plus the largest-contentful-paint figure. A slow site is the easiest problem to demonstrate on a call. The score is carried onto the CRM company record at import, where the conversation actually happens.
- Service pages — counts dedicated per-service pages in the business's sitemap. A business with one "Services" page and nothing under it is a business we can visibly help; one with nine service pages already does what we would sell them. The same crawl already has every URL on the site in hand, so it also picks out the business's contact page at no extra cost — see Recording outreach.
An audit that cannot reach a verdict — no website to test, a site that will not respond — is recorded as "no verdict", never as a failure. A failure is something you say out loud on a sales call, and it must never be an artefact of our own tooling breaking.
Importing into the CRM
Qualify the businesses worth pursuing, then Import qualified (or import one at a time).
Each import creates a CRM company carrying name, website, phone, address and the PageSpeed score, and a CRM contact whenever the business can be reached at all — by email, by phone, or through its own contact form. Only a business with none of those three imports as a company on its own.
Re-importing the same prospect updates both rather than forking a second one, and never blanks a field somebody has since filled in by hand in the CRM. (The contact is matched by the link the prospect stores, not by email address — a business that publishes no email has nothing to match on, so without that link every re-import would leave another copy behind.)
Contacts are tagged source = prospecting, which is what separates them from contact-form leads and what a CRM automation can scope on ("imported → create a follow-up task" is configuration, not code).
Recording outreach
Record outreach on any row asks two questions and stores both against the business:
- How you reached them — email, contact form, or phone. Pre-filled from what the business exposes: an email address if there is one, otherwise the contact form if they have a website, otherwise the phone. Change it if you did something else; what you say wins over what was guessed.
- What you sent — a custom video, or the generic message.
The second question is the one that matters and it is not optional. If the plan is a personal recording for the best prospects and a generic message for the rest, then whether that recording earns its two minutes is the most expensive open question in the whole operation — and it is only ever answerable by comparing reply rates between the two. Recorded at the moment of contact it costs nothing. Reconstructed a month later it cannot be done at all.
Once the business is in the CRM, the same action writes an interaction on its contact timeline, so the two screens never disagree about whether somebody has been approached. The interaction type follows the channel — email sends log as an email, phone as a call, and a contact form we filled in as a note, because CRM's form submission means a form came the other way. All three count as real touchpoints, so the contact stops showing as going cold.
The contact form is opened by a person, never submitted by us. Auto-filling other people's forms breaks most sites' terms, gets the server's IP blocked, and a templated form fill reads worse than a templated email would. The feature finds the URL and hands you a link.
The Outreach filter narrows the list to businesses nobody has written to yet, which is how you work through a long list without losing your place.
What it costs
Every search is a billed Google request — including each section of a sweep and the one lookup that resolves the area name to a box.
Google gives the first 1,000 searches of this kind away free every month, and that allowance is larger than it sounds: a suburb sweeps in single-digit searches and a city in tens. A normal month of prospecting therefore lands inside the free allowance and bills nothing at all. The rank-grid probes in SEO Scanner don't eat into it either — those use a cheaper tier that is free without limit.
The search budget on a sweep is a hard ceiling on what it will spend past that: the sweep pauses when it is reached and never goes beyond it. The panel shows searches used against the budget and a running cost estimate.
Treat the money figure as an estimate only, and one that only applies once you are past the free allowance. It is the list price of Google's Text Search Enterprise tier — $35 per 1,000 — which is what asking for rating, review count, website and phone puts a search into. Google reprices, and the rate steps down at higher volumes; the authority on what you are actually paying is the Google Cloud billing console.
A sense of scale: a rural county might sweep in a dozen searches. A large state is hundreds, because the metros subdivide repeatedly. Start with a budget you are comfortable with — the sweep pausing costs you nothing, and raising it resumes without re-searching a single section.
Is a complete list even the goal?
Usually not, and it is worth being honest about. If each prospect gets a personal follow-up, the realistic rate is a handful a day — so a complete list for a large state is years of outreach. The value of sweeping an area is being able to rank it and take the best few hundred, not completeness for its own sake. Sweep wide enough to rank well, then filter hard.
Google's terms
Google permits the place ID to be stored indefinitely; the descriptive fields are a refreshable snapshot and the schema treats them that way (refreshed_at on every prospect). Anything you intend to keep long term should be re-derived from the business's own website. Worth your own read of Google's Places API terms before running at volume.
Limitations
- Google never returns an email address. Plenty of small businesses hide theirs from scrapers and expect the contact form instead — which is a channel, not a disqualification, and arguably the better-read one, since it lands in the inbox they watch for work. Where an email is worth tracking down there is a hand-edit drawer on every row for it.
- 60 results per search is Google's limit, not ours, and Google's own documentation warns it may change. Sweeps detect a full section both by that count and by Google still offering another page, so the engine keeps working if the number moves.
- Sweeps have a detail floor. Sections still full at the finest level are reported by name rather than subdivided for ever.
- An area is swept for one category at a time. "Electrician" and "plumber" over Texas are two sweeps and two bills.
- A sweep of an area already being swept for the same category is refused — it would cost twice and find the same businesses.
Reference
| Dashboard | Prospecting (admin only — it spends money on a Google API and writes into the CRM) |
| Feature key | prospecting (requires crm) |
| API key setting | seo_scanner.places_api_key, shared with SEO Scanner |
| Background tasks | prospecting:sweep (every 2 min), prospecting:audit (every 5 min) |
| Commands | php artisan prospecting:sweep --limit=N, php artisan prospecting:audit --limit=N |
| Tables | prospects, prospect_searches, prospect_search_hits, prospect_audits, prospect_sweeps, prospect_coverage_cells |