Systems Library / Sales Automation / How to Build a Proposal Tracking and Follow-Up System
Sales Automation proposals documents

How to Build a Proposal Tracking and Follow-Up System

Track proposal views and trigger follow-ups when prospects engage.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

This proposal tracking and follow up automation system tells you exactly when prospects open your proposals and triggers follow-ups automatically. I use this to close the gap between sending and signing.

What You Need Before Starting

Step 1: Create Tracked Links

Generate a unique tracking URL for each proposal. When the prospect clicks it, you know they are looking. This replaces the blind send-and-hope approach.

import uuid
import sqlite3
from datetime import datetime

def create_tracked_proposal(deal_id, proposal_path):
    tracking_id = str(uuid.uuid4())[:8]
    conn = sqlite3.connect("proposals.db")
    conn.execute(
        "INSERT INTO tracked_proposals (tracking_id, deal_id, proposal_path, created_at) VALUES (?, ?, ?, ?)",
        (tracking_id, deal_id, proposal_path, datetime.now().isoformat())
    )
    conn.commit()
    return f"https://proposals.yourdomain.com/view/{tracking_id}"

Step 2: Log View Events

Serve the proposal through your tracking endpoint. Every page load gets logged with a timestamp and basic visitor info. This tells you not just if they opened it, but how many times.

from flask import Flask, request
from datetime import datetime

app = Flask(__name__)

@app.route("/view/<tracking_id>")
def track_view(tracking_id):
    log_event(tracking_id, {
        "timestamp": datetime.now().isoformat(),
        "ip": request.remote_addr,
        "user_agent": request.headers.get("User-Agent"),
    })
    return serve_proposal(get_path(tracking_id))

Step 3: Trigger Follow-Ups

First view triggers a soft follow-up 24 hours later. Three views without a response means escalation. The prospect is interested but stuck on something. That is when your sales rep needs to call.

def on_view(tracking_id):
    deal = get_deal(tracking_id)
    views = get_view_count(tracking_id)

    if views == 1:
        schedule_followup(deal["owner"], deal["id"], delay_hours=24,
            message=f"Prospect opened your proposal for {deal['name']}")

    if views >= 3 and not has_responded(deal["id"]):
        escalate_to_manager(deal["id"],
            reason="Viewed 3+ times without responding")

Step 4: Build Engagement Dashboard

Show all active proposals, their view counts, and which need attention. Sort by engagement so the hottest prospects surface to the top.

def proposal_dashboard():
    proposals = get_active_proposals()
    dashboard = []
    for p in proposals:
        views = get_view_count(p["id"])
        dashboard.append({
            "deal": p["deal_name"],
            "sent_date": p["created_at"],
            "views": views,
            "last_viewed": get_last_view(p["id"]),
            "status": "hot" if views >= 3 else "warm" if views >= 1 else "cold",
        })
    return sorted(dashboard, key=lambda x: x["views"], reverse=True)

Step 5: Measure Win Rates

Track which proposals convert. Over time this data shows you the average number of views before a close, the best day to send proposals, and which templates perform best.

def proposal_analytics(date_range):
    proposals = get_proposals(date_range)
    won = [p for p in proposals if p["outcome"] == "won"]
    return {
        "total_sent": len(proposals),
        "won": len(won),
        "win_rate": len(won) / max(len(proposals), 1),
        "avg_views_before_close": sum(p["views"] for p in won) / max(len(won), 1),
    }

What to Build Next

Add engagement scoring. Rank prospects by how much time they spend on each section.

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