5 Python Scripts That Automate the Most Tedious Parts of Running a Freelance Business
As a freelancer, every hour you spend on admin is an hour you're not billing. Here are 5 Python scripts I use to automate the repetitive work — invoice tracking, lead research, follow-up scheduling, time logging, and contract generation.
Each script is under 100 lines and works with tools you already use (Gmail, Google Sheets, Notion, CSV files).
1. Invoice Status Tracker — Python + Gmail API
This script scans your Gmail for sent invoices and automatically checks whether they've been paid by cross-referencing a CSV tracking file.
import csv
import datetime
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
def check_invoice_status(invoice_csv='invoices.csv'):
"""Check which sent invoices haven't been marked paid."""
with open(invoice_csv) as f:
reader = csv.DictReader(f)
invoices = [row for row in reader]
today = datetime.date.today()
overdue = []
for inv in invoices:
if inv['status'] != 'paid':
due = datetime.date.fromisoformat(inv['due_date'])
days_overdue = (today - due).days
if days_overdue > 0:
overdue.append({
'invoice': inv['invoice_number'],
'client': inv['client'],
'amount': inv['amount'],
'days_overdue': days_overdue
})
return sorted(overdue, key=lambda x: x['days_overdue'], reverse=True)
if __name__ == '__main__':
overdue = check_invoice_status()
if overdue:
print(f"⚠️ {len(overdue)} overdue invoice(s):")
for inv in overdue:
print(f" {inv['invoice']} — {inv['client']} — {inv['amount']} — {inv['days_overdue']}d overdue")
else:
print("✅ All invoices current")
Run this every morning. It takes 2 seconds and prevents the "wait, did they ever pay invoice #47?" panic.
2. Lead Finder — Scrape HN "Who is Hiring?" for Your Skills
This is the core of my HN Startup Hunter tool. You search all recent HN hiring threads for startups that match your skills:
import requests
import re
def search_hn_jobs(skills: list[str], months: int = 3) -> list[dict]:
"""Search HN Who is Hiring threads for jobs matching your skills."""
# Get recent Who is Hiring thread IDs from Algolia
url = "https://hn.algolia.com/api/v1/search"
params = {
'query': 'Ask HN: Who is hiring?',
'tags': 'story',
'hitsPerPage': months
}
resp = requests.get(url, params=params)
threads = resp.json()['hits']
results = []
for thread in threads:
thread_id = thread['objectID']
# Get all comments in this thread
comments_url = f"https://hn.algolia.com/api/v1/search"
c_params = {'tags': f'comment,story_{thread_id}', 'hitsPerPage': 500}
comments = requests.get(comments_url, params=c_params).json()['hits']
for comment in comments:
text = comment.get('comment_text', '').lower()
if any(skill.lower() in text for skill in skills):
# Extract email if present
emails = re.findall(r'[\w.-]+@[\w.-]+\.\w+', text)
results.append({
'author': comment.get('author'),
'email': emails[0] if emails else None,
'text_preview': text[:200],
'url': f"https://news.ycombinator.com/item?id={comment['objectID']}"
})
return results
# Example: find Python + data engineering jobs
leads = search_hn_jobs(['python', 'data engineer', 'ETL'])
for lead in leads[:10]:
print(f"- {lead['author']}: {lead['email'] or 'no email'} | {lead['text_preview'][:100]}")
Or just use the pre-built UI if you don't want to run the script yourself.
3. Follow-Up Scheduler — Never Miss a Lead Again
This script reads a contacts CSV and tells you which leads to follow up with today based on a configurable cadence:
import csv
import datetime
FOLLOW_UP_DAYS = {1: 3, 2: 7, 3: 14} # 3d, 7d, 14d gaps
def get_todays_followups(contacts_csv='contacts.csv'):
today = datetime.date.today()
follow_up = []
with open(contacts_csv) as f:
for row in csv.DictReader(f):
if row['status'] not in ('closed', 'dropped'):
last_contact = datetime.date.fromisoformat(row['last_contact_date'])
follow_up_count = int(row.get('follow_up_count', 0))
if follow_up_count >= 2:
continue # max 2 follow-ups
days_since = (today - last_contact).days
required_gap = FOLLOW_UP_DAYS.get(follow_up_count + 1, 999)
if days_since >= required_gap:
follow_up.append({
'name': row['name'],
'email': row['email'],
'company': row['company'],
'days_since': days_since,
'follow_up_number': follow_up_count + 1
})
return follow_up
followups = get_todays_followups()
if followups:
print(f"📬 {len(followups)} follow-up(s) due today:")
for f in followups:
print(f" → {f['name']} ({f['company']}) | {f['days_since']}d since last contact | FU #{f['follow_up_number']}")
4. Time Logger — Automatic Hours Tracking from Git Commits
If you use git, this script approximates your hours worked from commit timestamps:
import subprocess
import datetime
from collections import defaultdict
def estimate_hours_from_git(repo_path='.', days_back=30):
"""Estimate hours worked from git commit history."""
since = (datetime.datetime.now() - datetime.timedelta(days=days_back)).strftime('%Y-%m-%d')
result = subprocess.run(
['git', '-C', repo_path, 'log', f'--since={since}',
'--format=%ai', '--author=YOU'],
capture_output=True, text=True
)
timestamps = []
for line in result.stdout.strip().split('\n'):
if line:
dt = datetime.datetime.fromisoformat(line[:19])
timestamps.append(dt)
if not timestamps:
return {}
timestamps.sort()
daily_hours = defaultdict(float)
for i in range(1, len(timestamps)):
gap = (timestamps[i] - timestamps[i-1]).total_seconds() / 3600
# Only count gaps under 2 hours as 'active work'
if gap < 2:
day = timestamps[i].date().isoformat()
daily_hours[day] += gap
return dict(daily_hours)
hours = estimate_hours_from_git()
for day, h in sorted(hours.items()):
print(f"{day}: {h:.1f}h")
print(f"Total: {sum(hours.values()):.1f}h")
5. Contract Generator — Fill a Template from a Config Dict
Stop rewriting contracts from scratch. Keep a template and fill it programmatically:
from pathlib import Path
import datetime
CONTRACT_TEMPLATE = """
FREELANCE SERVICES AGREEMENT
Date: {date}
Client: {client_name} ({client_company})
Freelancer: {freelancer_name}
Scope of Work:
{scope}
Deliverables:
{deliverables}
Payment Terms:
- Total: {currency}{amount}
- Deposit: {currency}{deposit} due on signing
- Balance: {currency}{balance} due on delivery
- Payment method: {payment_method}
Timeline:
- Start: {start_date}
- Delivery: {deadline}
IP Ownership:
All work product created under this agreement is work-for-hire.
"""
def generate_contract(config: dict) -> str:
config['date'] = datetime.date.today().isoformat()
config['deposit'] = int(config['amount'] * 0.5)
config['balance'] = config['amount'] - config['deposit']
return CONTRACT_TEMPLATE.format(**config)
# Usage
contract = generate_contract({
'client_name': 'Jane Doe',
'client_company': 'Acme Corp',
'freelancer_name': 'Your Name',
'scope': 'Build a data ingestion pipeline for Salesforce → BigQuery',
'deliverables': '- Tested Python script\n- Documentation\n- 30-day bug support',
'currency': '$',
'amount': 800,
'payment_method': 'Wise / bank transfer',
'start_date': '2026-06-01',
'deadline': '2026-06-15'
})
print(contract)
Getting the Full Templates
All these scripts tie together in a complete freelance management system. If you want pre-built CSV templates for tracking clients, projects, invoices, and leads — the Freelance Business Starter Kit has all four, ready to import into any spreadsheet or Notion:
→ Freelance Business Starter Kit — $9 on Gumroad
Or if you want me to build a custom automation for your specific workflow, the done-for-you service is $75 for a 24h turnaround:
Got a workflow you want automated? Drop a comment — I reply to all of them.
Top comments (0)