Systems Library / Industry Applications / How to Automate Rental Property Management Tasks
Industry Applications real estate

How to Automate Rental Property Management Tasks

Automate maintenance requests, rent collection, and tenant communication.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Automating rental property management tasks handles the operational load that grows with every unit you add. Maintenance requests, rent reminders, lease renewals, tenant communications. I built this system for a property manager with 45 units who was drowning in manual work. Now the system handles routine operations and only surfaces things that need a human decision.

What You Need Before Starting

Step 1: Set Up the Property Management Database

import sqlite3
from datetime import datetime

def init_pm_db(db_path="property_mgmt.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS units (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            address TEXT,
            unit_number TEXT,
            rent_amount REAL,
            tenant_name TEXT,
            tenant_email TEXT,
            tenant_phone TEXT,
            lease_start TEXT,
            lease_end TEXT,
            status TEXT DEFAULT 'occupied'
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS maintenance (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            unit_id INTEGER,
            description TEXT,
            priority TEXT DEFAULT 'normal',
            status TEXT DEFAULT 'open',
            submitted_at TEXT,
            resolved_at TEXT,
            vendor TEXT,
            cost REAL
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS payments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            unit_id INTEGER,
            amount REAL,
            due_date TEXT,
            paid_date TEXT,
            status TEXT DEFAULT 'pending'
        )
    """)
    conn.commit()
    conn.close()

Step 2: Automate Rent Reminders

from datetime import timedelta
import smtplib
from email.mime.text import MIMEText

def send_rent_reminders(db_path="property_mgmt.db"):
    conn = sqlite3.connect(db_path)
    
    upcoming = conn.execute("""
        SELECT p.id, u.tenant_name, u.tenant_email, u.address, u.unit_number, p.amount, p.due_date
        FROM payments p JOIN units u ON p.unit_id = u.id
        WHERE p.status = 'pending'
        AND p.due_date BETWEEN date('now') AND date('now', '+5 days')
    """).fetchall()
    
    for payment in upcoming:
        pid, name, email, address, unit, amount, due = payment
        send_email(
            to=email,
            subject=f"Rent reminder: ${amount:.0f} due {due}",
            body=f"Hi {name},\n\nThis is a friendly reminder that rent of ${amount:.2f} for {address} Unit {unit} is due on {due}.\n\nPlease let us know if you have any questions.\n\nThank you"
        )
        print(f"Sent reminder to {name} for ${amount}")
    
    conn.close()
    return len(upcoming)

def check_late_payments(db_path="property_mgmt.db"):
    conn = sqlite3.connect(db_path)
    late = conn.execute("""
        SELECT p.id, u.tenant_name, u.tenant_email, u.address, u.unit_number, p.amount, p.due_date
        FROM payments p JOIN units u ON p.unit_id = u.id
        WHERE p.status = 'pending' AND p.due_date < date('now')
    """).fetchall()
    conn.close()
    return late

Step 3: Build the Maintenance Request Handler

import anthropic
from dotenv import load_dotenv
load_dotenv()

def classify_maintenance_request(description):
    client = anthropic.Anthropic()
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=256,
        system="""Classify this maintenance request. Return JSON with:
- priority: "emergency" (flooding, gas leak, no heat), "urgent" (broken AC, plumbing leak), "normal" (cosmetic, minor repair)
- category: plumbing, electrical, hvac, appliance, structural, pest, cosmetic, other
- needs_vendor: true/false""",
        messages=[{"role": "user", "content": f"Maintenance request: {description}"}]
    )
    
    import json
    try:
        return json.loads(response.content[0].text)
    except json.JSONDecodeError:
        return {"priority": "normal", "category": "other", "needs_vendor": True}

def submit_maintenance_request(unit_id, description, db_path="property_mgmt.db"):
    classification = classify_maintenance_request(description)
    
    conn = sqlite3.connect(db_path)
    conn.execute("""
        INSERT INTO maintenance (unit_id, description, priority, submitted_at)
        VALUES (?,?,?,?)
    """, (unit_id, description, classification["priority"], datetime.utcnow().isoformat()))
    conn.commit()
    conn.close()
    
    if classification["priority"] == "emergency":
        notify_manager(f"EMERGENCY maintenance request for unit {unit_id}: {description}")
    
    return classification

Step 4: Lease Renewal Automation

def check_lease_renewals(days_ahead=60, db_path="property_mgmt.db"):
    conn = sqlite3.connect(db_path)
    expiring = conn.execute("""
        SELECT id, tenant_name, tenant_email, address, unit_number, lease_end, rent_amount
        FROM units
        WHERE status = 'occupied'
        AND lease_end BETWEEN date('now') AND date('now', ?)
    """, (f"+{days_ahead} days",)).fetchall()
    conn.close()
    
    renewals = []
    for unit in expiring:
        uid, name, email, addr, unit_num, end_date, rent = unit
        renewals.append({
            "unit_id": uid, "tenant": name, "email": email,
            "address": f"{addr} Unit {unit_num}",
            "expires": end_date, "current_rent": rent
        })
    
    return renewals

def send_renewal_notice(renewal):
    send_email(
        to=renewal["email"],
        subject=f"Lease renewal for {renewal['address']}",
        body=f"Hi {renewal['tenant']},\n\nYour lease at {renewal['address']} expires on {renewal['expires']}. We would love to have you stay.\n\nPlease let us know if you plan to renew and we can prepare the paperwork.\n\nThank you"
    )

Step 5: Daily Management Report

def daily_pm_report(db_path="property_mgmt.db"):
    conn = sqlite3.connect(db_path)
    
    open_maintenance = conn.execute("SELECT COUNT(*) FROM maintenance WHERE status = 'open'").fetchone()[0]
    emergency = conn.execute("SELECT COUNT(*) FROM maintenance WHERE status = 'open' AND priority = 'emergency'").fetchone()[0]
    late_payments = len(check_late_payments(db_path))
    renewals = len(check_lease_renewals(30, db_path))
    
    conn.close()
    
    return f"""Daily Property Report:
  Open maintenance: {open_maintenance} ({emergency} emergency)
  Late payments: {late_payments}
  Leases expiring within 30 days: {renewals}"""

if __name__ == "__main__":
    send_rent_reminders()
    print(daily_pm_report())

What to Build Next

Add a tenant portal where residents submit maintenance requests through a web form. The AI classifier triages instantly and the right vendor gets notified for emergency issues.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems