Systems Library / Finance Automation / How to Automate Client Retainer Billing and Tracking
Finance Automation invoicing billing

How to Automate Client Retainer Billing and Tracking

Track retainer usage and generate billing statements automatically.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Retainer clients pay a fixed amount but usage varies. I built a system to automate retainer billing and track usage so both you and the client know exactly where hours stand. Monthly statements, overage alerts, and rollover calculations all happen automatically.

Transparency builds trust. This system provides it.

What You Need Before Starting

Step 1: Define Retainer Contracts

RETAINER_CONTRACTS = {
    "acme-001": {
        "client": "Acme Corp",
        "monthly_hours": 40,
        "hourly_rate": 150,
        "monthly_fee": 5000,
        "overage_rate": 175,
        "rollover_hours": True,
        "max_rollover": 10
    }
}

Step 2: Track Usage Against Retainer

import sqlite3
from datetime import datetime

def init_retainer_db(db_path="retainers.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS retainer_usage (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            client_id TEXT,
            month TEXT,
            hours_used REAL DEFAULT 0,
            hours_allocated REAL,
            rollover_hours REAL DEFAULT 0,
            overage_hours REAL DEFAULT 0,
            overage_amount REAL DEFAULT 0
        )
    """)
    conn.commit()
    return conn

def log_hours(conn, client_id, hours, description):
    month = datetime.now().strftime("%Y-%m")
    conn.execute(
        "UPDATE retainer_usage SET hours_used = hours_used + ? WHERE client_id=? AND month=?",
        (hours, client_id, month)
    )
    conn.commit()

Step 3: Calculate Monthly Retainer Status

def calculate_retainer_status(conn, client_id, month):
    contract = RETAINER_CONTRACTS[client_id]
    usage = conn.execute(
        "SELECT hours_used, hours_allocated, rollover_hours FROM retainer_usage WHERE client_id=? AND month=?",
        (client_id, month)
    ).fetchone()

    if not usage:
        return None

    hours_used, allocated, rollover = usage
    total_available = allocated + rollover
    remaining = total_available - hours_used
    overage = max(0, hours_used - total_available)
    overage_cost = overage * contract["overage_rate"]

    return {
        "client": contract["client"],
        "hours_allocated": allocated,
        "rollover_from_last": rollover,
        "total_available": total_available,
        "hours_used": hours_used,
        "remaining": max(0, remaining),
        "overage_hours": overage,
        "overage_cost": overage_cost,
        "utilization": round(hours_used / total_available * 100, 1) if total_available else 0
    }

Step 4: Generate Monthly Statement

def generate_retainer_statement(status, month):
    statement = f"# Retainer Statement: {status['client']}\n"
    statement += f"## {month}\n\n"
    statement += f"| Metric | Value |\n|---|---|\n"
    statement += f"| Hours Allocated | {status['hours_allocated']} |\n"
    statement += f"| Rollover Hours | {status['rollover_from_last']} |\n"
    statement += f"| Total Available | {status['total_available']} |\n"
    statement += f"| Hours Used | {status['hours_used']} |\n"
    statement += f"| Remaining | {status['remaining']} |\n"
    statement += f"| Utilization | {status['utilization']}% |\n"

    if status["overage_hours"] > 0:
        statement += f"\n**Overage: {status['overage_hours']} hours at ${RETAINER_CONTRACTS.get('acme-001', {}).get('overage_rate', 0)}/hr = ${status['overage_cost']:.2f}**\n"

    return statement

Step 5: Alert on Threshold

def check_retainer_alerts(conn):
    month = datetime.now().strftime("%Y-%m")
    alerts = []

    for client_id, contract in RETAINER_CONTRACTS.items():
        status = calculate_retainer_status(conn, client_id, month)
        if not status:
            continue

        if status["utilization"] >= 90:
            alerts.append({"client": contract["client"], "type": "near_limit",
                          "message": f"{status['utilization']}% utilized. {status['remaining']} hours remaining."})
        elif status["overage_hours"] > 0:
            alerts.append({"client": contract["client"], "type": "overage",
                          "message": f"Overage: {status['overage_hours']}h (${status['overage_cost']:.2f})"})

    return alerts

What to Build Next

Add automatic scope recommendations when clients consistently use more or less than their allocation. Upsell clients who always go over. Downgrade clients who consistently underuse. Data-driven account management.

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