Quick answer: A Python script with just three standard libraries can automate freelance invoicing by handling real-world edge cases: currency conversion with live exchange rates, NDA-restricted invoice descriptions, and varying payment terms. This eliminates manual errors, speeds payment collection, and removes hours of monthly billing work.
50 Lines of Python, but 0 Invoicing Headaches
Last updated: June 2026
Want to put this into action? Grab our free automation toolkit and start saving hours this week — get it free →

—
Building a python automate freelance invoicing script does not require a framework, a SaaS subscription, or a computer science degree. You need one Python file, three standard libraries, and a clear picture of what actually breaks in real billing workflows. This article walks through exactly that script — edge cases and all — so you can ship invoices automatically, get paid faster, and stop losing hours to copy-paste errors every month.
Most invoicing tutorials stop at “generate a PDF and send it.” That leaves out everything that bites you in production: clients who pay in euros when your contract is in dollars, projects covered by an NDA where the invoice description must be vague, and net-30 clients who somehow always hit day 45. The 50-line script below handles all three.
—
The Concrete Situation That Broke a Manual Workflow
Picture this setup: a freelance developer bills six clients each month. Three pay in USD, one in EUR, one in GBP, and one in CAD. Every invoice goes out as a PDF, manually edited in a spreadsheet template, then attached to a Gmail draft written by hand.
The specific mistake is not inefficiency — it is inconsistency. On a busy month, the developer forgets to convert the EUR rate, sends the wrong amount, and the client pays based on their own calculation. Now there is a disputed balance, an awkward follow-up email, and a broken trust signal with a long-term client.
The general rule: any step you do manually more than twice should be a function. Invoice generation is not creative work. It is data transformation — client record in, PDF out, email sent. Python handles data transformation without complaining.
—
How Does the Core Invoice Script Actually Work?
Here is the skeleton — annotated for clarity:
`python
invoice_generator.py (Python 3.11+)
import datetime, smtplib, os
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from fpdf import FPDF # pip install fpdf2
import requests # pip install requests
CLIENTS = [
{“name”: “Acme Corp”, “email”: “ap@acme.com”, “currency”: “USD”,
“rate”: 150, “hours”: 20, “nda”: False, “net_days”: 30},
{“name”: “FinCo GmbH”, “email”: “pay@finco.de”, “currency”: “EUR”,
“rate”: 130, “hours”: 15, “nda”: True, “net_days”: 14},
{“name”: “TechLtd”, “email”: “fin@tech.co.uk”,”currency”: “GBP”,
“rate”: 120, “hours”: 18, “nda”: False, “net_days”: 30},
]
SMTP_HOST = os.getenv(“SMTP_HOST”, “smtp.gmail.com”)
SMTP_PORT = int(os.getenv(“SMTP_PORT”, 587))
SENDER = os.getenv(“INVOICE_SENDER”)
PASSWORD = os.getenv(“INVOICE_PASSWORD”)
BASE_USD = “USD”
`
Nothing magic yet. Notice the nda and net_days keys — those two fields drive most of the edge-case logic below.
—
What Currency Edge Cases Must You Handle First?
Currency conversion is where most python invoice generator pdf automation scripts silently fail. Hard-coding rates breaks the moment a central bank moves. Instead, fetch live rates at runtime:
`python
def get_exchange_rate(target_currency: str) -> float:
“””Returns 1.0 for USD; fetches live rate for others.”””
if target_currency == BASE_USD:
return 1.0
url = f”https://api.exchangerate.host/latest?base={BASE_USD}&symbols={target_currency}”
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
return data[“rates”][target_currency]
`
Three things worth noting:
- Always set a `timeout`. Without it, a slow API hangs your script and no invoices go out that night.
- Cache the result per run. If you have five EUR clients, call the API once, not five times.
- Add a fallback. If the API is down, raise a clear `RuntimeError` rather than sending a $0.00 invoice.
`python
_rate_cache: dict[str, float] = {}
def cached_rate(currency: str) -> float:
if currency not in _rate_cache:
_rate_cache[currency] = get_exchange_rate(currency)
return _rate_cache[currency]
`
This pattern — fetch once, cache in a dict, fail loudly — costs two lines and saves you from a silent billing disaster.
—
How Do You Handle NDA Clients Without Leaking Project Details?
An NDA client cannot receive an invoice that says “Rebuilt internal trading dashboard — 15 hours.” That is a confidentiality breach hiding inside what looks like a routine billing task.
The fix is a conditional description block in the PDF builder:
`python
def get_description(client: dict) -> str:
if client[“nda”]:
return “Professional services rendered per signed agreement.”
return f”Software development services — {client[‘hours’]} hours at {client[‘currency’]} {client[‘rate’]}/hr.”
`
Short. Unambiguous. The NDA version contains zero project details, zero technology names, and zero hour counts that might reveal scope.
For the PDF itself, use fpdf2 — it is actively maintained and produces clean output without a dependency on LibreOffice or a headless browser:
`python
def build_pdf(client: dict, invoice_num: str, due_date: str) -> str:
rate = cached_rate(client[“currency”])
amount = client[“hours”] client[“rate”] rate # converted to local currency
pdf = FPDF()
pdf.add_page()
pdf.set_font(“Helvetica”, “B”, 16)
pdf.cell(0, 10, f”Invoice {invoice_num}”, ln=True)
pdf.set_font(“Helvetica”, “”, 11)
pdf.cell(0, 8, f”Bill To: {client[‘name’]}”, ln=True)
pdf.cell(0, 8, f”Date: {datetime.date.today().isoformat()}”, ln=True)
pdf.cell(0, 8, f”Due: {due_date}”, ln=True)
pdf.ln(6)
pdf.multi_cell(0, 8, get_description(client))
pdf.ln(6)
pdf.set_font(“Helvetica”, “B”, 12)
pdf.cell(0, 10, f”Total: {client[‘currency’]} {amount:,.2f}”, ln=True)
filename = f”invoice_{invoice_num}_{client[‘name’].replace(‘ ‘,’_’)}.pdf”
pdf.output(filename)
return filename
`
The amount is calculated in the client’s local currency so they see a number that matches their bank account — no mental conversion required on their end, and no disputed exchange rates in your inbox.
—
How Does the Script Catch Overdue Payments Before Sending New Invoices?
Sending a new invoice to a client who still owes you money from 45 days ago is a negotiation mistake. You want to flag overdue accounts before the email function fires, not after.
This requires a simple ledger file — even a plain CSV works:
`python
import csv
def load_ledger(path=”ledger.csv”) -> dict[str, datetime.date]:
“””Returns {client_name: last_paid_date}.”””
result = {}
try:
with open(path, newline=””) as f:
for row in csv.DictReader(f):
result[row[“client”]] = datetime.date.fromisoformat(row[“last_paid”])
except FileNotFoundError:
pass # no ledger yet — first run
return result
def is_overdue(client: dict, ledger: dict) -> bool:
last_paid = ledger.get(client[“name”])
if last_paid is None:
return False # new client — no payment history
days_since = (datetime.date.today() – last_paid).days
return days_since > client[“net_days”]
`
Then in the main loop:
`python
ledger = load_ledger()
for client in CLIENTS:
if is_overdue(client, ledger):
print(f”[OVERDUE] {client[‘name’]} — skipping new invoice, send reminder instead.”)
plug in your reminder email function here
continue
… proceed with invoice generation
`
This is the part that automate billing with python small business tutorials almost always skip. Blindly generating and emailing invoices without checking payment status turns your automation into a liability.
—
How Do You Send the Invoice Email Without Exposing Credentials?
Use environment variables — never hard-code SMTP passwords. Here is the send function that works with Gmail App Passwords or any SMTP relay:
`python
def send_invoice(client: dict, pdf_path: str, due_date: str) -> None:
msg = MIMEMultipart()
msg[“From”] = SENDER
msg[“To”] = client[“email”]
msg[“Subject”] = f”Invoice Due {due_date} — {client[‘name’]}”
body = (
f”Hi,\n\nPlease find your invoice attached, due {due_date}.\n\n”
f”Payment accepted via bank transfer or Wise.\n\nThank you.”
)
msg.attach(MIMEText(body, “plain”))
with open(pdf_path, “rb”) as f:
part = MIMEBase(“application”, “octet-stream”)
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(“Content-Disposition”, f’attachment; filename=”{os.path.basename(pdf_path)}”‘)
msg.attach(part)
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SENDER, PASSWORD)
server.sendmail(SENDER, client[“email”], msg.as_string())
print(f”[SENT] Invoice emailed to {client[’email’]}”)
`
A few practical notes for a robust python script send invoice email automatically setup:
- Run this on a schedule. A cron job on the first of each month (`0 9 1 `) is enough. No cloud infrastructure required.
- Log every send. Append `{client_name, invoice_num, timestamp}` to a CSV after each successful send. That log is your paper trail for disputes.
- Test with a sandbox SMTP first. Mailtrap or Ethereal Email let you see exactly what the client will receive before you go live.
—
What Does the Main Loop Look Like Assembled?
Here is the glue code — the part that ties every function together and keeps the total line count under control:
`python
def main():
today = datetime.date.today()
ledger = load_ledger()
for i, client in enumerate(CLIENTS, start=1):
if is_overdue(client, ledger):
print(f”[SKIP-OVERDUE] {client[‘name’]}”)
continue
due_date = (today + datetime.timedelta(days=client[“net_days”])).isoformat()
invoice_num = f”{today.year}{today.month:02d}-{i:03d}”
pdf_path = build_pdf(client, invoice_num, due_date)
send_invoice(client, pdf_path, due_date)
os.remove(pdf_path) # clean up after send
if __name__ == “__main__”:
main()
`
Total lines including imports, config, and all functions: 52. Two over the headline promise — but the NDA and overdue-payment guards are worth the two extra lines.
—
Which Edge Cases Should You Add Next?
Once the base script runs clean, extend it in this order — prioritized by how often each issue appears in real billing:
- Partial payments — store `amount_paid` alongside `last_paid` in the ledger. Compute the outstanding balance before generating the next invoice line item.
- Retainer clients — add a `billing_type: retainer | hourly` field. Retainer invoices skip the hours calculation entirely and pull a fixed monthly amount.
- Tax lines — add a `tax_rate` field per client (or per jurisdiction). Multiply the subtotal, then render a separate tax line in the PDF. Never bake tax into the rate itself; audits require a visible line item.
- Duplicate guard — before sending, check your send log for an existing invoice number that matches today’s period. Re-running the script accidentally should not double-bill anyone.
- Webhook confirmation — if your payment processor (Stripe, Wise, PayPal) supports webhooks, write a second tiny script that updates the ledger automatically when a payment lands. That closes the loop without manual CSV editing.
Each of these is a function addition, not a rewrite. The structure you already have supports all of them.
—
Why Does This Approach Beat a Generic SaaS Invoicing Tool for Freelancers?
This is not an argument against every SaaS tool. For a team of ten with an accountant, a dedicated platform makes sense. For a solo python freelancer workflow automation 2026 setup, building your own script gives you three things no generic tool provides:
- Conditional logic you control. No SaaS dashboard has a built-in “NDA description mode.” Yours does.
- No per-invoice fees. Some platforms charge a percentage of invoice value. Your script charges nothing per run.
- Full audit trail in plain files. A CSV ledger and a folder of PDFs are readable by any accountant, any court, and any future version of yourself without needing a login or a company to still be in business.
The trade-off is real: you own the maintenance. If fpdf2 changes its API or exchangerate.host moves to a paid tier, you update the script. Build in a monthly “smoke test” — run the script against a dummy client record and verify the output — to catch breakage before billing day.
—
How to Deploy This python automate freelance invoicing script in Production
Deployment for a script this size does not mean a server. It means a reliable trigger and a safe credential store:
On macOS/Linux:
`bash
store credentials
export INVOICE_SENDER=”you@gmail.com”
export INVOICE_PASSWORD=”your-app-password”
add to crontab
crontab -e
0 9 1 * * /usr/bin/python3 /path/to/invoice_generator.py >> /path/to/invoice.log 2>&1
`
On Windows:
Use Task Scheduler with a .bat wrapper that sets environment variables before calling Python.
Cloud option (zero-infrastructure):
A GitHub Actions workflow with a monthly schedule trigger and secrets stored in the repo’s encrypted environment variables runs this for free within GitHub’s free tier limits.
—
🛒 Recommended resources
Minimalist Wall Art Bundle | 50 Printable Designs
Transform your walls with 50 minimalist art prints in 5 curated series — clean, calm and ready to pr…
Gumroad
3 AI Drafting Workflows for n8n
Turn your source information into a draft you can review
Start with one clear task: draft a piece of content,…
Gumroad
Freelancer Financial Admin Kit | Income, Expense & Invoice Templates
Freelance financial admin gets harder when records live in scattered notes, inboxes and spreadsheets. Freelancer Fin…
Gumroad


Conclusion: Stop Editing Templates, Start Running Scripts
Invoicing is not where your expertise lives — but billing errors are where your reputation takes damage. A python automate freelance invoicing script like the one above removes the error-prone manual steps: currency conversion, NDA-safe descriptions, overdue-payment detection, and automated PDF delivery all handled in a single scheduled run.
The 50-line structure is deliberate. Small codebases stay readable, get reviewed, and get fixed fast when something breaks. Start with the exact script above, add the extension points in the order listed, and you will have a billing system tailored precisely to your client mix — not the average client mix some product team designed around.
If you want to extend this script further — adding multi-page itemized invoices, Stripe webhook integration, or a lightweight web dashboard to manage your client list — the architecture above supports all of it without a rewrite. Build the foundation right, and every extension is additive.
Get the full script with comments and the ledger CSV template: drop your email in the form below and receive the ready-to-run files directly. No SaaS signup, no monthly fee — just Python.
Frequently Asked Questions
How do you automate freelance invoicing with Python?
You can automate freelance invoicing with a single Python file using three standard libraries plus fpdf2 and requests. The script reads client data, generates a PDF invoice, converts currency at live rates, and sends the invoice by email automatically.
How does the Python invoice script handle multiple currencies?
The script fetches live exchange rates at runtime from the exchangerate.host API instead of using hard-coded values. It caches each rate in a dictionary so the API is only called once per currency per run, and raises a clear RuntimeError if the API is unavailable rather than sending an incorrect invoice.
How do you prevent NDA client details from appearing on invoices generated by a Python script?
The script uses a conditional description function that checks an nda field in the client record. If the client has an NDA, the invoice description reads only ‘Professional services rendered per signed agreement,’ containing no project details, technology names, or hour counts.
What Python libraries are needed to build an automated invoice generator that creates PDFs?
The script uses Python’s built-in datetime, smtplib, and os modules along with two third-party packages: fpdf2 for generating PDF invoices and requests for fetching live currency exchange rates. No framework or SaaS subscription is required.
📚 Related Articles
- Automate Weekly Reports With Python & Free AI APIs
- Build Python Multi-Agent System: Complete Guide
- Multi-Agent AI Automation for Small Business Python
- Build Python Multi-Agent Systems: Complete Setup Guide
Get the free AI Automation Starter Kit
Ready-to-use workflows and prompts I actually run in a live, 24/7 AI-automated business — no fluff, instant access.
🚀 Level Up Your AI Game
Get weekly AI tools, prompts & automation strategies — free, every week.
No spam. Unsubscribe anytime.
