Follow-up is where property leads are won, and where bots turn into spam. The difference is almost always the stop rules. This post shows a small scheduler pattern for AI automation for real estate agencies with follow-up timers that stop: a data model, a sequence, the stop logic, and a worker loop.
Data model
One row per lead in an active sequence.
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class FollowUp:
lead_id: str
agent_id: str
step: int # 0 = first reply already sent
next_at: datetime
status: str # "active" | "paused" | "stopped"
stop_reason: Optional[str] = None
The sequence
Keep it short and explicit. Each step says what kind of message it is, because that decides who sends it.
SEQUENCE = [
{"after_hours": 2, "kind": "agent_draft"}, # AI drafts, agent sends
{"after_hours": 48, "kind": "template_nudge"}, # pre-approved template
{"after_hours": 120, "kind": "related_listing"}, # pre-approved template
]
Stop rules (the important part)
def should_stop(lead, last_msg) -> Optional[str]:
if last_msg and last_msg.from_lead:
return "replied" # a human takes over, timers end
if lead.status in ("not_interested", "bought_elsewhere", "under_offer"):
return "closed"
if lead.opted_out:
return "opt_out"
if lead.step >= len(SEQUENCE):
return "sequence_done"
return None
A reply from the lead always stops automation and alerts the agent. That single rule prevents most of the embarrassing cases, like a nudge arriving an hour after the buyer booked a viewing.
The worker
def tick(now):
for f in due_followups(now):
lead = get_lead(f.lead_id)
reason = should_stop(lead, last_message(f.lead_id))
if reason:
stop(f, reason)
continue
step = SEQUENCE[f.step]
if step["kind"] == "agent_draft":
queue_for_agent(f, draft_from_history(f.lead_id))
else:
send_template(f, step["kind"])
advance(f) # step += 1, compute next_at from the next step
Run tick from a scheduler every few minutes. Make advance and stop atomic with the send, or use an outbox table, so a crash cannot send the same nudge twice.
Notes from practice
- Pause, do not delete. If an agent is on leave, pause their sequences and resume them, rather than letting them run unattended.
- Templates outside the window. On WhatsApp, business-initiated messages outside the customer service window need pre-approved templates, so keep the nudge steps as templates.
- Log every stop reason. Over a few weeks it tells you which listings and which messages actually get replies.
-
Quiet hours. Clamp
next_atto sensible local hours for the lead, not the server.
Disclosure: I co-founded NxFlowAI (Mumbai). When we scope a follow-up workflow like this, the first audit takes 72 hours.
Top comments (0)