# Follow-Up Timers for Property Leads: A Small Scheduler With Stop Rules

> Source: <https://dev.to/ujjwal_dubey_9/follow-up-timers-for-property-leads-a-small-scheduler-with-stop-rules-34d8>
> Published: 2026-10-19 05:03:02+00:00

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](https://www.nxflowai.com/services): a data model, a sequence, the stop logic, and a worker loop.

One row per lead in an active sequence.

``` python
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
```

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
]
php
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.

``` python
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.

`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.*
