Building a Real Estate Lead Nurturing Agent: From Inquiry to Showing to Close
Build an AI agent that scores real estate leads, runs personalized drip campaigns, schedules property showings, and automates follow-up sequences from first contact to closing.
The Real Estate Lead Problem
A busy real estate agent gets 50 leads per month from Zillow, their website, open houses, and referrals. Without consistent follow-up, 80% of those leads go cold. Studies show it takes 8-12 touchpoints before a lead converts. An AI nurturing agent manages this pipeline — scoring leads, sending personalized communications, scheduling showings, and escalating hot leads to the human agent.
Lead Scoring Model
We start by scoring leads based on their behavior and profile attributes.
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
class LeadStage(Enum):
NEW = "new"
ENGAGED = "engaged"
SHOWING_SCHEDULED = "showing_scheduled"
OFFER_STAGE = "offer_stage"
UNDER_CONTRACT = "under_contract"
CLOSED = "closed"
COLD = "cold"
@dataclass
class Lead:
lead_id: str
name: str
email: str
phone: str
source: str # zillow, website, referral, open_house
stage: LeadStage
budget_min: float
budget_max: float
preferred_areas: list[str]
bedrooms_min: int
pre_approved: bool
timeline: str # immediately, 1-3 months, 3-6 months, exploring
interactions: list[dict] = field(default_factory=list)
score: int = 0
def calculate_lead_score(lead: Lead) -> int:
"""Score a lead from 0-100 based on readiness signals."""
score = 0
# Source quality
source_scores = {
"referral": 25, "open_house": 20,
"website": 15, "zillow": 10,
}
score += source_scores.get(lead.source, 5)
# Financial readiness
if lead.pre_approved:
score += 25
# Timeline urgency
timeline_scores = {
"immediately": 25, "1-3 months": 15,
"3-6 months": 5, "exploring": 0,
}
score += timeline_scores.get(lead.timeline, 0)
# Engagement recency
if lead.interactions:
last = lead.interactions[-1]
days_since = (datetime.now() - datetime.fromisoformat(last["date"])).days
if days_since <= 2:
score += 15
elif days_since <= 7:
score += 10
elif days_since <= 14:
score += 5
# Engagement depth
score += min(10, len(lead.interactions) * 2)
return min(100, score)
Leads scoring above 70 are "hot" and get immediate human attention. Leads between 30-70 enter automated nurture sequences. Below 30 get low-frequency check-ins.
Drip Campaign Engine
The agent sends personalized messages based on the lead's stage and interests.
from typing import Callable
@dataclass
class DripMessage:
day_offset: int # days after entering the sequence
subject: str
template: str
channel: str # email, sms
BUYER_DRIP_SEQUENCE = [
DripMessage(
day_offset=0,
subject="Welcome, {name} — Your Home Search Starts Here",
template="""Hi {name},
Thanks for reaching out about properties in {areas}. I have put
together some listings in your {budget_min}-{budget_max} range
that I think you will love.
Here are 3 matches: {listing_links}
Want to schedule a showing? Reply to this email or pick a time
on my calendar: {calendar_link}""",
channel="email",
),
DripMessage(
day_offset=3,
subject="New listings in {primary_area} this week",
template="""Hi {name}, {new_count} new listings hit the market
in {primary_area} this week. Here are the top matches for your
criteria: {listing_links}""",
channel="email",
),
DripMessage(
day_offset=7,
subject=None,
template="""Hi {name}, just checking in — did any of those
{primary_area} listings catch your eye? Happy to set up showings
this weekend if you are interested.""",
channel="sms",
),
]
async def get_next_drip_message(
lead: Lead,
sequence: list[DripMessage],
days_in_sequence: int,
) -> Optional[DripMessage]:
"""Determine the next drip message to send."""
sent_offsets = {
i["day_offset"]
for i in lead.interactions
if i.get("type") == "drip"
}
for msg in sequence:
if msg.day_offset <= days_in_sequence and msg.day_offset not in sent_offsets:
return msg
return None
Showing Scheduler
When a lead expresses interest, the agent books showings automatically.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
from agents import function_tool
@function_tool
async def schedule_showing(
lead_id: str,
listing_ids: str,
preferred_date: str,
preferred_time: str,
) -> str:
"""Schedule property showings for a lead."""
listings = [lid.strip() for lid in listing_ids.split(",")]
# In production: check agent calendar, confirm with listing agents,
# create calendar events, send confirmations
showing_count = len(listings)
return (
f"Scheduled {showing_count} showing(s) for {preferred_date} "
f"starting at {preferred_time}.\n"
f"Confirmation sent to lead and listing agents.\n"
f"Route optimized for minimum drive time between properties."
)
@function_tool
async def get_lead_pipeline(stage: str = "all") -> str:
"""Get a summary of leads in the pipeline by stage."""
return (
"Pipeline Summary:\n"
"- New: 12 leads (avg score: 35)\n"
"- Engaged: 8 leads (avg score: 55)\n"
"- Showing Scheduled: 5 leads (avg score: 72)\n"
"- Offer Stage: 2 leads (avg score: 88)\n"
"- Under Contract: 1 lead\n"
"Hot leads needing attention: Sarah M. (score: 85), James K. (score: 78)"
)
Follow-Up Automation
After showings, the agent sends tailored follow-ups.
@function_tool
async def send_post_showing_followup(
lead_id: str,
listing_id: str,
showing_notes: str,
) -> str:
"""Send a personalized follow-up after a property showing."""
# In production: the LLM crafts a personalized message
# based on the showing notes and lead preferences
return (
"Follow-up email sent with:\n"
"- Personalized recap of the showing\n"
"- Comparable sales data for the neighborhood\n"
"- Mortgage payment estimate based on their budget\n"
"- Link to schedule a second showing or make an offer"
)
@function_tool
async def escalate_hot_lead(
lead_id: str,
reason: str,
) -> str:
"""Alert the human agent about a high-priority lead."""
return (
f"ALERT sent to agent: Lead {lead_id} needs immediate attention. "
f"Reason: {reason}. Lead profile and full interaction history attached."
)
The Lead Nurturing Agent
from agents import Agent
lead_agent = Agent(
name="LeadNurturingAgent",
instructions="""You are a real estate lead nurturing specialist.
Your job is to keep leads engaged until they are ready to buy.
Score leads, send appropriate communications, schedule showings,
and escalate hot leads to the human agent.
Rules:
- Never pressure leads. Be helpful and informative.
- Respect communication preferences (email vs SMS).
- Escalate leads scoring above 75 for human follow-up.
- Log every interaction for the lead's history.""",
tools=[
schedule_showing,
get_lead_pipeline,
send_post_showing_followup,
escalate_hot_lead,
],
)
FAQ
How does the agent avoid being too aggressive with follow-ups?
The drip sequence has built-in cooling periods. If a lead does not respond to 3 consecutive messages, the agent reduces frequency to bi-weekly. After 30 days of no engagement, the lead moves to "cold" status with monthly market updates only. The lead can re-engage at any time and re-enter the active sequence.
Can the agent personalize messages for different buyer personas?
Yes. The drip templates use variables populated from the lead's profile — preferred areas, budget range, bedroom requirements. The LLM generates the actual message content, so it naturally adapts tone and detail level based on the lead's engagement history and stated preferences.
How do you measure the agent's effectiveness?
Key metrics include lead-to-showing conversion rate, average response time, number of touchpoints before conversion, and pipeline velocity (time from new lead to close). The agent logs all interactions with timestamps, making it straightforward to compute these metrics and compare against manual follow-up performance.
#LeadNurturing #RealEstateCRM #SalesAutomation #Python #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.