Real Estate Lead Nurture
Module 19 · business · intermediate
Lesson 19 · Real Estate Lead Nurture
Goal
Real estate leads go cold in minutes: whoever replies first usually gets the showing. Build a system that answers every inquiry immediately (text + email, from the assigned agent, with similar listings) and keeps following up for a week without anyone remembering to.
Prerequisites
- Modules 05 (Gmail, follow-ups), 13 (static data), 15 (injected clock), 16 (continue-on-error reports)
- For the live part: Gmail, and a Twilio number for SMS
Concepts
- One workflow, many entry points. New leads, the drip scheduler, stop requests and status checks are separate triggers in one workflow. All of them pass through Settings, and a Switch routes by which trigger ran (
$('Drip Run API').isExecuted). They share one registry in workflow static data, which is only possible inside a single workflow. - Parse anything. Leads arrive as clean JSON (website form) or as a portal notification email. The parser reads labeled lines (
Name:,Email:,Phone:,Property:,Listing ID:), falls back to regexes for any email/phone/street address, detects the portal from the sender/subject, and infers intent (rent/lease → renter, "home worth"/"selling" → seller). - Scheduler instead of long waits. A 7-day
Waitnode is fragile to edit and hard to test. Instead, each lead stores its drip steps withdue_atdates, and an hourly job sends whatever is due. Injectingnowmakes the whole schedule testable in seconds. - Round robin. A counter in static data assigns new leads to agents in turn; duplicates keep their agent.
- Stop is part of the design. Every drip must end when the lead replies, books or opts out. The stop endpoint is what your inbox/SMS/CRM automation calls (Exercise 2).
- Compliance. Texting leads is regulated (e.g. TCPA in the US, plus carrier rules for business texting). Only text people who gave consent (portal inquiries usually include it, but check), identify yourself, include opt-out wording, and honor STOP immediately. This is not legal advice; check your market's rules.
Build it
Step 1: Triggers, Settings, Route
- Triggers: Webhook
New Lead API(POSTre/lead), Gmail TriggerPortal Lead Emails(Simplify off, queryfrom:(zillow.com OR realtor.com OR homes.com OR redfin.com) newer_than:1d, unread), ScheduleDrip Every Hour, WebhookDrip Run API(POSTre/drip/run), WebhookStop Lead API(POSTre/lead/stop), WebhookLead Status API(GETre/lead/status). - Edit Fields
Settings (edit me): brokerage, booking link,agents(name/email/phone),sms_from,sms_enabled,dedupe_days,dripsteps, and demolistings(id, address, city, price, beds, baths, type sale/rent, status, listed_at, url). - Switch
Routewith four renamed outputs: new lead, drip, stop, status.
Step 2: New lead lane
- Code
Parse Lead(workflow/code/parse.js) → IFHas Contact?(email or phone) → 400 otherwise. - Code
Match Listings(match.js): the asked listing by id or address; similar = same type, same city, active, price within ±20%, closest first, max 3 (none for sellers). - Code
Register Lead(register.js): dedupe key = email or phone; new → next agent (round robin) + drip schedule; duplicate → same agent, same schedule,inquiries + 1. Skipped on dry runs. - Code
Compose Replies(compose.js): SMS ≤ 320 chars ending with "Reply STOP to opt out."; email with similar homes and a pre-approval nudge for buyers who aren't; separate seller and duplicate wording. - IF
Dry Run?→ Respond plan. Else IFText Them?(phone andsms_enabled) → Twilio SMS → Send (fromsms_from) → Gmail Send (sender name = agent); both On Error → Continue. - Code
Lead Report(SMS ok if asidcame back, email ok if aniddid) → IFFrom API?→ Respond; plusAlert Agent (Exercise 1).
Step 3: Drip lane
- Code
Find Due Messages(due.js), Always Output Data on: for active leads, every unsent step withdue_at <= now, rendered from three templates (check-in, listings, last chance). - IF
Send Now?(has a key and not dry run) → GmailSend Drip Email(On Error → Continue). - Code
Record Drip Results(record-drip.js): setsent_aton success,last_erroron failure (retried next run) → respond if called via API.
Step 4: Stop and status lanes
Code Stop Drip (stop.js) sets status: stopped and lists the cancelled steps (404 if unknown). Code Lead Status (status.js) returns the record.
Publish (static data only persists for production executions).
Test it
./test/test.sh # 30 checks, no credentials
WITH_CREDS=1 TEST_EMAIL=you@example.com TEST_PHONE=+1... ./test/test.sh # real SMS + email
Watch the drip without waiting a week:
curl -X POST http://localhost:5678/webhook/re/drip/run -H 'Content-Type: application/json' \
-d '{"now":"2026-12-01T10:00:00Z","dry_run":true}'
Common problems
- Parser misses fields from a real portal email: open the Gmail trigger output and adjust the labels in
parse.js(field('Name')…) to that portal's layout. - Every lead goes to the same agent: you're testing with dry runs (they don't advance the counter) or running manually (static data isn't saved).
- Drip sends twice: two schedules running (e.g. a duplicate workflow), or
sent_atnot saved because the run was manual. - SMS fails with a Twilio error about the sender:
sms_frommust be a number on your Twilio account, and US business texting may require registration (10DLC) first. - Leads keep getting emails after replying: nothing calls the stop endpoint yet (Exercise 2).