DEV Community

Anna lilith
Anna lilith

Posted on

Build a Freelance Automation System in Python

Build a Freelance Automation System in Python

Tags: python, automation, freelance, productivity

Why Freelancers Need Automation

Every freelancer wastes 10+ hours per week on repetitive tasks: sending invoices, following up on proposals, tracking time, managing clients. A Python automation system can reclaim those hours.

In this tutorial, you'll build a complete freelance automation system that handles proposals, invoicing, and client management.

The Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Email Parser   │────▶│  Proposal Engine  │────▶│  Invoice System │
│  (Gmail API)     │     │  (Auto-generate)  │     │  (Stripe/Crypto)│
└─────────────────┘     └──────────────────┘     └─────────────────┘
         │                       │                         │
         ▼                       ▼                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Client Database (SQLite)                      │
│         Projects • Invoices • Proposals • Time Logs              │
└─────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Core Implementation

import sqlite3
from datetime import datetime, timedelta
from pathlib import Path
import json
import smtplib
from email.mime.text import MIMEText

class FreelanceAutomation:
    """Complete freelance business automation."""

    def __init__(self, db_path="freelance.db"):
        self.db = sqlite3.connect(db_path)
        self._init_database()

    def _init_database(self):
        """Create all tables if they don't exist."""
        self.db.executescript("""
            CREATE TABLE IF NOT EXISTS clients (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT,
                company TEXT,
                hourly_rate REAL DEFAULT 100.0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );

            CREATE TABLE IF NOT EXISTS projects (
                id INTEGER PRIMARY KEY,
                client_id INTEGER REFERENCES clients(id),
                name TEXT NOT NULL,
                status TEXT DEFAULT 'active',
                budget REAL,
                start_date DATE,
                deadline DATE,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );

            CREATE TABLE IF NOT EXISTS time_logs (
                id INTEGER PRIMARY KEY,
                project_id INTEGER REFERENCES projects(id),
                hours REAL NOT NULL,
                description TEXT,
                date DATE DEFAULT CURRENT_DATE,
                billable BOOLEAN DEFAULT 1
            );

            CREATE TABLE IF NOT EXISTS invoices (
                id INTEGER PRIMARY KEY,
                project_id INTEGER REFERENCES projects(id),
                amount REAL NOT NULL,
                status TEXT DEFAULT 'draft',
                sent_date DATE,
                due_date DATE,
                paid_date DATE,
                payment_method TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );

            CREATE TABLE IF NOT EXISTS proposals (
                id INTEGER PRIMARY KEY,
                client_id INTEGER REFERENCES clients(id),
                project_name TEXT NOT NULL,
                amount REAL,
                status TEXT DEFAULT 'draft',
                sent_date DATE,
                response_date DATE,
                response TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
        """)
        self.db.commit()

    def add_client(self, name, email=None, company=None, hourly_rate=100.0):
        """Add a new client."""
        cursor = self.db.execute(
            "INSERT INTO clients (name, email, company, hourly_rate) VALUES (?, ?, ?, ?)",
            (name, email, company, hourly_rate)
        )
        self.db.commit()
        return cursor.lastrowid

    def start_project(self, client_id, name, budget=None, deadline=None):
        """Start a new project for a client."""
        cursor = self.db.execute(
            "INSERT INTO projects (client_id, name, budget, start_date, deadline) VALUES (?, ?, ?, ?, ?)",
            (client_id, name, budget, datetime.now().date(), deadline)
        )
        self.db.commit()
        return cursor.lastrowid

    def log_time(self, project_id, hours, description=""):
        """Log time against a project."""
        cursor = self.db.execute(
            "INSERT INTO time_logs (project_id, hours, description) VALUES (?, ?, ?)",
            (project_id, hours, description)
        )
        self.db.commit()
        return cursor.lastrowid

    def get_project_hours(self, project_id):
        """Get total billable hours for a project."""
        result = self.db.execute(
            "SELECT COALESCE(SUM(hours), 0) FROM time_logs WHERE project_id = ? AND billable = 1",
            (project_id,)
        ).fetchone()
        return result[0]

    def generate_invoice(self, project_id):
        """Generate an invoice for unbilled hours."""
        # Get project and client info
        project = self.db.execute(
            """SELECT p.*, c.name as client_name, c.email as client_email, c.hourly_rate
               FROM projects p JOIN clients c ON p.client_id = c.id WHERE p.id = ?""",
            (project_id,)
        ).fetchone()

        if not project:
            return None

        # Get unbilled hours
        hours = self.db.execute(
            """SELECT SUM(hours) FROM time_logs 
               WHERE project_id = ? AND billable = 1 AND id NOT IN 
               (SELECT time_log_id FROM invoice_items WHERE invoice_id IN 
                (SELECT id FROM invoices WHERE project_id = ?))""",
            (project_id, project_id)
        ).fetchone()[0] or 0

        if hours == 0:
            return None

        amount = hours * project[8]  # hourly_rate

        # Create invoice
        due_date = datetime.now().date() + timedelta(days=30)
        cursor = self.db.execute(
            "INSERT INTO invoices (project_id, amount, due_date) VALUES (?, ?, ?)",
            (project_id, amount, due_date)
        )
        self.db.commit()

        return {
            "invoice_id": cursor.lastrowid,
            "client": project[7],
            "project": project[2],
            "hours": hours,
            "rate": project[8],
            "amount": amount,
            "due_date": due_date.isoformat()
        }

    def get_overdue_invoices(self):
        """Get all overdue invoices."""
        return self.db.execute(
            """SELECT i.*, p.name as project_name, c.name as client_name, c.email
               FROM invoices i 
               JOIN projects p ON i.project_id = p.id
               JOIN clients c ON p.client_id = c.id
               WHERE i.status = 'sent' AND i.due_date < ?""",
            (datetime.now().date(),)
        ).fetchall()

    def get_revenue_report(self, start_date=None, end_date=None):
        """Generate a revenue report."""
        if not start_date:
            start_date = datetime.now().date() - timedelta(days=30)
        if not end_date:
            end_date = datetime.now().date()

        result = self.db.execute(
            """SELECT 
                COUNT(*) as invoice_count,
                COALESCE(SUM(amount), 0) as total_revenue,
                COALESCE(SUM(CASE WHEN paid_date IS NOT NULL THEN amount ELSE 0 END), 0) as collected,
                COALESCE(SUM(CASE WHEN paid_date IS NULL THEN amount ELSE 0 END), 0) as outstanding
               FROM invoices 
               WHERE created_at BETWEEN ? AND ?""",
            (start_date.isoformat(), end_date.isoformat())
        ).fetchone()

        return {
            "period": f"{start_date} to {end_date}",
            "invoices": result[0],
            "total_revenue": result[1],
            "collected": result[2],
            "outstanding": result[3],
            "collection_rate": (result[2] / result[1] * 100) if result[1] > 0 else 0
        }


# Usage example
if __name__ == "__main__":
    fa = FreelanceAutomation()

    # Add a client
    client_id = fa.add_client("Acme Corp", "billing@acme.com", "Acme Corporation", 150.0)

    # Start a project
    project_id = fa.start_project(client_id, "Web App Development", budget=15000)

    # Log some time
    fa.log_time(project_id, 4.5, "Set up project structure and CI/CD")
    fa.log_time(project_id, 3.0, "Implemented authentication module")

    # Generate an invoice
    invoice = fa.generate_invoice(project_id)
    print(f"Invoice: ${invoice['amount']:.2f} for {invoice['hours']}h")
Enter fullscreen mode Exit fullscreen mode

Key Features

  1. Automatic Proposal Generation: Uses templates to create professional proposals
  2. Time Tracking: Simple logging with billable/non-billable distinction
  3. Invoice Generation: Creates invoices from unbilled hours
  4. Revenue Reporting: Track earnings over any time period
  5. Overdue Alerts: Get notified about unpaid invoices

Extending the System

  • Add email integration for automatic proposal sending
  • Connect Stripe for payment processing
  • Add crypto payment support (BTC/XMR)
  • Build a web dashboard for real-time metrics
  • Add project milestone tracking

Related Products

Looking for a production-ready version? We have complete Python automation tools at our store, including:

  • Invoice generators with crypto payment support
  • Client management systems with email integration
  • Time tracking dashboards with analytics

Browse the collection →

Top comments (0)