DEV Community

Brad
Brad

Posted on

5 Python Scripts That Automate the Most Tedious Parts of Running a Freelance Business

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")
Enter fullscreen mode Exit fullscreen mode

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]}")
Enter fullscreen mode Exit fullscreen mode

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']}")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

Custom HN Lead Report — $75


Got a workflow you want automated? Drop a comment — I reply to all of them.

Top comments (0)