How to Build a Proposal Tracking and Follow-Up System
Track proposal views and trigger follow-ups when prospects engage.
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
- Python 3.8+
- Claude or GPT API key
- Jinja2 for templating
- weasyprint for PDF generation
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
- Creating an Automated Affiliate Tracking System - automated affiliate tracking system
- The Automation Decision Tree - automation decision tree framework
- Building Your First Automation: A Complete Guide - first automation guide business
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