The Lots That Sold for $100
Finds Brevard County vacant lots that changed hands for almost nothing and emails one buyer a worked list every Friday
The Problem
A small buyer in Brevard County places manufactured homes on vacant lots, one lot at a time, and had no list to work from. The lots she wants show up in the county's transfer history as sales recorded at $0 to $1,500: quit claims, family transfers, estate deeds, the deals nobody advertised. That history is published only as a 1.9 GB Access database refreshed daily, and whether a manufactured home is even allowed on a given lot lives in a separate zoning map. Her alternative was calling the Planning Department one parcel number at a time.
What It Does
The whole county property roll is loaded into a database and checked before it replaces the last copy. Every week the feed pulls vacant residential parcels that changed hands for $0 to $1,500 since the last batch, skips anything already delivered, and drops retired parcel IDs into an exclusion log instead of silently losing them. Each lot gets its zoning district from the county map, and each owner with a real person's name is run through a skip trace for phones and emails. A workbook is built with a Fits Criteria column and a Why Not column against her size, price and flood rules, then a verifier checks the file before anyone sees it. Thursday night an email lands in Ryan's inbox with six stat tiles and the workbook attached. Friday he opens a second workflow, confirms the date and recipient, and it sends the file to her, stamps the ledger so those lots never come back, and logs the delivery.
A real estate buyer who acquires individual vacant lots for manufactured home placement and works from a printed list with letters, postcards and phone calls. The feed is exclusive to one subscriber.
Outcome
The first 60-day batch surfaced 159 nominal-consideration vacant lots, 34 of which fit her buy box, from a county file no buyer could read by hand. The weekly feed picks up where that batch stopped. The first Friday file went out on 2026-09-04 with 6 parcels; the following week the build ran, found nothing new, and said so instead of sending an empty file. The first weeks are slim because the county publishes transfers about six weeks after they happen.
Screenshots
How It's Built
The county appraiser publishes the whole roll as a zip holding an Access database. A loader script unzips it, refuses to run if the column layout changed, stages both tables as text, compares row counts and date parse rates against the live tables, then swaps them in one transaction and rebuilds a per-parcel sales summary. Workflow 1 opens an SSH session to the server and runs a shell wrapper that chains the weekly extract SQL, a zoning lookup against county and Palm Bay GIS, skip trace prep and submission to the trace vendor's API, the workbook builder and the verifier. The extract joins transfers to parcels on property ID, keeps the five vacant land use codes, drops inactive parcels to an exclusion log, and pulls only rows the delivered ledger has no delivered timestamp for, stamping them as staged. A code node parses the wrapper's final RESULT line, throws if there is none, separates NOTE lines from warnings, and lifts the row, phone, fit and trace counts out of the log. An IF node routes OK to a second SSH call that base64-encodes the workbook, then a code node builds an HTML approval email with six tiles and sends it through Resend to Ryan; EMPTY routes to a plain nothing-new email. Workflow 2 starts from a Set node holding the send date and recipient, re-runs the verifier over SSH, reads the email body from a text file and base64-encodes the file in the same call, refuses to continue unless the verifier printed ALL CHECKS PASSED and line one of the body starts with Subject:, sends through Resend, captures the message ID, then branches: a live recipient stamps the ledger, parses the stamp result, logs the delivery to an Airtable Deliveries table and emails Ryan a confirmation; any other recipient is test mode, which sends with a [TEST] subject and leaves the ledger and Airtable untouched.
By the numbers
- transfer rows in the county roll
- 1,917,407
- parcels in the county roll
- 393,772
- county roll file size
- 1.91 GB Access database, 235 MB zipped
- roll load runtime
- about 10 minutes
- nominal price band
- $0 to $1,500
- recorded sale price on first-batch rows
- $100 on all 159
- first backfill window
- 2026-05-28 to 2026-07-28, 159 transfers
- first batch by deed type
- 98 quit claim, 53 warranty, 5 estate, 3 foreclosure
- first batch fitting her buy box
- 34 of 159
- nominal vacant-land transfers per month, 0.20 to 2.00 acres
- about 90
- county publication lag
- 4 to 6 weeks
- zoning coverage on the 90-day sample
- 137 of 158 parcels
- first weekly file, delivered 2026-09-04
- 6 parcels, 4 fitting her criteria, 1 traceable owner, 0 phones
- second weekly build, 2026-09-10
- nothing new, no file sent
- build workflow runtime, 2026-09-03 run with 6 rows
- 1 minute 51 seconds
- build workflow runtime, 2026-09-10 empty run
- 43 seconds
- verifier checks printed as passed on the 2026-09-04 file
- 15
- skip trace cost per successful hit
- 2 cents
- approval email stat tiles
- 6
- n8n nodes across both workflows
- 24
- build workflow proven runs before first delivery
- 5
- build schedule
- Thursdays 21:00 Central
- delivery cadence
- weekly, Fridays, one subscriber
Key Decisions
Split build and send into two workflows with a human in between
The Thursday build can run unattended because nothing leaves the house until Ryan has read the stat tiles and the run log. Doing nothing is the safe default: rows stay unstamped and roll into next week on their own.
Build Thursday at 21:00 and send Friday morning
She wanted her new data on Fridays. Pulling as late as Thursday night gives her the freshest possible file, and the send waits for the next morning so a person has looked at it.
Set the nominal band ceiling at $1,500
An old habit. Different counties record non-market transfers at different placeholder amounts, sometimes $0, sometimes $100, so $1,500 is wide enough to catch whatever a given county uses without ever missing one.
Key the delivered ledger on book, page and property ID, and stamp delivered only from an approved send
One recorded instrument can convey several parcels, so the instrument alone is not unique. Staging and delivering are separate stamps so a rejected build self-heals into the next one instead of losing rows.
Test mode is triggered by the recipient address, and the default recipient is Ryan
Forgetting to change the field costs a test email to himself, not an unrecallable email to the client. Stamping the ledger on a test would mean those parcels never reach her, so test mode never stamps.
Throw when the wrapper prints no RESULT line, when the base64 attachment size disagrees with the reported byte count, or when the verifier does not print ALL CHECKS PASSED
A silent partial run is worse than a failed one. Each check turns a quiet bad file into a loud stopped workflow before an email goes out.
Count trace matched separately from trace hits, and say which one is zero in the email
A join failure and a vendor with no phone for a decedent look identical in the phone count and mean completely different things. The tile now reads either JOIN FAILED or joined fine, vendor had none.
Keep NOTE lines out of the warning count
One informational line contained the phrase not a join failure, which the warning regex swept up and counted as an alarm on a clean week.
Make the ledger stamp failure loud after the email is already gone
If the send succeeds and the stamp does not, the file is with the client but the ledger still thinks it is pending. The workflow throws with the exact command to run by hand rather than logging a delivery it cannot back.
Let the Airtable log fail without stopping the confirmation
The delivery already happened and the ledger is already stamped by that point. A logging hiccup should not look like a failed send.
Flag her criteria in a Fits Criteria and Why Not column instead of filtering rows out
She asked to see everything, including parcels over two acres, so the feed carries no acreage filter and lets her decide.
Drop inactive parcels to an exclusion file with a reason rather than silently
A retired parcel ID dead-ends on the appraiser site, but a count of what was excluded and why belongs in the approval email.
Send an email even when there is nothing to send
A quiet Thursday is indistinguishable from a broken one unless the run says so. The empty notice states that nothing was built, that the county is weeks behind, and that the ledger still holds everything undelivered.
What broke & how it was fixed
The first n8n runs all aborted before doing any work. The shell wrapper guarded against overlapping runs with pgrep on its own name, and when n8n launched it the wrapper shell also carried the script path, so the count was two and it exited. It was replaced with flock on a lock file and tested against four cases. Separately, the trace step warned on a normal week: it treated zero phone hits as a join failure when the join had worked and the vendor simply had no phone for a deceased owner. The fix counts matched rows apart from hits, and the approval email now says which one is zero. Earlier, appending the SSH public key with cat welded it onto an existing line with no trailing newline, which broke both keys and let sshd fall back to password auth without the test noticing; every key test now forces batch mode with password auth off.
Where else this applies
The shape is a bulk government dataset that is too big to read, filtered to a narrow signal, deduped against a ledger of what was already sent, enriched from two more public sources, and delivered on a schedule with a human approval gate in front of the send. Any county roll, court docket or licensing file that is published whole and refreshed daily fits it. The two-workflow split, build unattended then send on approval, is the part that transfers most cleanly: it lets a one-person operation run nightly automation against a paying client without ever sending something unread.