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_at to 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.