Generating customer receipts, contracts, and monthly invoices manually consumes hours of administrative time and introduces calculation mistakes. In automated billing systems, your application needs to receive transaction records, generate a formatted PDF with company branding, calculate taxes and totals, and dispatch the PDF directly to the customer's email via SMTP.
In this tutorial, you will build an end-to-end billing automation pipeline using ReportLab (for deterministic PDF canvas layout) and Python's built-in smtplib and email modules (for authenticated SSL/TLS email delivery).
Python PDF Generation Libraries Compared
| Library | Approach | Dependencies | Pros | Cons |
|---|---|---|---|---|
| ReportLab | Programmatic Canvas & Flowables | Pure Python / C-extension | Pixel-perfect layouts, fast generation | Steeper coordinate learning curve |
| WeasyPrint | HTML + CSS to PDF | Cairo, Pango, GDK-PixBuf | Design invoices with standard CSS | Heavy external C-libraries to install |
| pdfkit | wkhtmltopdf wrapper | wkhtmltopdf binary | Quick setup from HTML strings | Deprecated upstream engine |
Installation
pip install reportlab
Building the Invoice PDF Generator with ReportLab Flowables
import os
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
def generate_invoice_pdf(filename: str, invoice_number: str, customer_info: dict, items: list) -> str:
doc = SimpleDocTemplate(filename, pagesize=letter, rightMargin=36, leftMargin=36, topMargin=36, bottomMargin=36)
story = []
styles = getSampleStyleSheet()
# 1. Header Branding
title_style = ParagraphStyle(
'InvoiceTitle',
parent=styles['Heading1'],
fontSize=22,
textColor=colors.HexColor("#1a365d"),
spaceAfter=12
)
story.append(Paragraph("iLab Academy - Tax Invoice", title_style))
story.append(Paragraph(f"Invoice #: {invoice_number}
Customer: {customer_info['name']}
Email: {customer_info['email']}", styles['Normal']))
story.append(Spacer(1, 20))
# 2. Line Items Table Data
table_data = [["Item Description", "Qty", "Unit Price", "Total Price"]]
subtotal = 0.0
for item in items:
line_total = item['qty'] * item['price']
subtotal += line_total
table_data.append([item['name'], str(item['qty']), f"${item['price']:.2f}", f"${line_total:.2f}"])
tax = subtotal * 0.10
total = subtotal + tax
table_data.append(["", "", "Subtotal:", f"${subtotal:.2f}"])
table_data.append(["", "", "Tax (10%):", f"${tax:.2f}"])
table_data.append(["", "", "Grand Total:", f"${total:.2f}"])
# 3. Table Styling
invoice_table = Table(table_data, colWidths=[280, 50, 90, 100])
invoice_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#f1f5f9")),
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor("#0f172a")),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
('GRID', (0, 0), (-1, -4), 0.5, colors.HexColor("#cbd5e1")),
('FONTNAME', (2, -3), (-1, -1), 'Helvetica-Bold'),
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
]))
story.append(invoice_table)
story.append(Spacer(1, 30))
story.append(Paragraph("Payment is due within 14 business days. Thank you for your business!", styles['Normal']))
doc.build(story)
print(f"Generated PDF invoice: {filename}")
return filename
if __name__ == "__main__":
sample_customer = {"name": "Dev Studio Ltd", "email": "billing@devstudio.com"}
sample_items = [
{"name": "Python Backend Architecture Consultation", "qty": 10, "price": 120.00},
{"name": "API Security Audit & Penetration Test", "qty": 1, "price": 850.00}
]
generate_invoice_pdf("invoice_1001.pdf", "INV-2026-1001", sample_customer, sample_items)
Automating Email Dispatch with smtplib and MIME Attachments
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
def send_invoice_email(recipient_email: str, invoice_path: str, smtp_user: str, smtp_pass: str):
msg = MIMEMultipart()
msg['From'] = smtp_user
msg['To'] = recipient_email
msg['Subject'] = "Your Invoice from iLab Academy"
body = "Hello,\n\nPlease find your latest invoice attached to this email.\n\nBest regards,\niLab Academy Billing Team"
msg.attach(MIMEText(body, 'plain'))
with open(invoice_path, "rb") as f:
attachment = MIMEApplication(f.read(), _subtype="pdf")
attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(invoice_path))
msg.attach(attachment)
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(smtp_user, smtp_pass)
server.send_message(msg)
print(f"Invoice successfully sent to {recipient_email}!")
Frequently Asked Questions
Q: How do I handle multi-page tables in ReportLab?
A: When using SimpleDocTemplate and Table Flowables, ReportLab automatically splits long item rows across multiple pages and recalculates coordinates directly.
0 Comments