Systems Library / Finance Automation / How to Build an AI Invoice Generator
Finance Automation invoicing billing

How to Build an AI Invoice Generator

Generate and send professional invoices automatically from project data.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Creating invoices manually from project data is tedious and error-prone. I built an ai invoice generator for automated billing that pulls hours, expenses, and deliverables from your project tracking system and produces ready-to-send invoices. No spreadsheet wrangling.

Data flows from project to invoice without human copying.

What You Need Before Starting

Step 1: Define Invoice Data Structure

from datetime import datetime
from dataclasses import dataclass, field

@dataclass
class InvoiceItem:
    description: str
    quantity: float
    rate: float
    amount: float = 0

    def __post_init__(self):
        self.amount = round(self.quantity * self.rate, 2)

@dataclass
class Invoice:
    invoice_number: str
    client_name: str
    client_email: str
    items: list = field(default_factory=list)
    due_date: str = ""
    notes: str = ""

    @property
    def subtotal(self):
        return round(sum(item.amount for item in self.items), 2)

    @property
    def tax(self):
        return round(self.subtotal * 0.0, 2)

    @property
    def total(self):
        return round(self.subtotal + self.tax, 2)

Step 2: Pull Data from Time Tracking

def build_invoice_from_project(project_data, client_info, invoice_number):
    items = []
    for entry in project_data["time_entries"]:
        items.append(InvoiceItem(
            description=f"{entry['task']} ({entry['date']})",
            quantity=entry["hours"],
            rate=client_info["hourly_rate"]
        ))

    for expense in project_data.get("expenses", []):
        items.append(InvoiceItem(
            description=f"Expense: {expense['description']}",
            quantity=1,
            rate=expense["amount"]
        ))

    due_date = (datetime.now() + __import__('datetime').timedelta(days=30)).strftime("%Y-%m-%d")

    return Invoice(
        invoice_number=invoice_number,
        client_name=client_info["name"],
        client_email=client_info["email"],
        items=items,
        due_date=due_date
    )

Step 3: Generate Invoice HTML

def generate_invoice_html(invoice):
    items_html = ""
    for item in invoice.items:
        items_html += f"""
        <tr>
            <td>{item.description}</td>
            <td>{item.quantity}</td>
            <td>${item.rate:.2f}</td>
            <td>${item.amount:.2f}</td>
        </tr>"""

    return f"""<!DOCTYPE html>
<html>
<head><style>
    body {{ font-family: Arial, sans-serif; margin: 40px; }}
    table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
    th, td {{ padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }}
    .total {{ font-weight: bold; font-size: 1.2em; }}
</style></head>
<body>
    <h1>Invoice #{invoice.invoice_number}</h1>
    <p>Bill to: {invoice.client_name}</p>
    <p>Date: {datetime.now().strftime('%B %d, %Y')}</p>
    <p>Due: {invoice.due_date}</p>

    <table>
        <tr><th>Description</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
        {items_html}
    </table>

    <p class="total">Total: ${invoice.total:.2f}</p>
    <p>{invoice.notes}</p>
</body></html>"""

Step 4: Convert to PDF

def save_invoice_pdf(html_content, output_path):
    try:
        from weasyprint import HTML
        HTML(string=html_content).write_pdf(output_path)
    except ImportError:
        from pathlib import Path
        Path(output_path.replace('.pdf', '.html')).write_text(html_content)

    return output_path

Step 5: Send the Invoice

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders

def send_invoice(invoice, pdf_path, smtp_config):
    msg = MIMEMultipart()
    msg["Subject"] = f"Invoice #{invoice.invoice_number} - {invoice.client_name}"
    msg["To"] = invoice.client_email

    body = f"Hi {invoice.client_name},\n\nPlease find attached invoice #{invoice.invoice_number} for ${invoice.total:.2f}.\n\nDue date: {invoice.due_date}\n\nThank you."
    msg.attach(MIMEText(body))

    with open(pdf_path, "rb") as f:
        attachment = MIMEBase("application", "pdf")
        attachment.set_payload(f.read())
        encoders.encode_base64(attachment)
        attachment.add_header("Content-Disposition", f"attachment; filename=invoice-{invoice.invoice_number}.pdf")
        msg.attach(attachment)

    with smtplib.SMTP(smtp_config["host"], smtp_config["port"]) as server:
        server.starttls()
        server.login(smtp_config["user"], smtp_config["password"])
        server.send_message(msg)

    print(f"Invoice #{invoice.invoice_number} sent to {invoice.client_email}")

What to Build Next

Add payment tracking that matches incoming payments to invoices. When a payment clears, the invoice status updates automatically and triggers a thank-you email. Generation is step one. Collection is where the money moves.

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