I Automated the Late Payment Follow-Up (Here's the Script)
Source: Dev.to
If you’ve been freelancing for more than a month, you’ve lived this: invoice sent, due date passed, radio silence.
The follow‑up email is easy to write once, but annoying to write on a schedule for every client, consistently. So most people don’t — and late payments drag on.
I automated it.
What the script does
It reads a CSV of your invoices (client name, amount, due date, email, status). Once a day, it checks for overdue invoices and sends the right email for the right stage:
- Day 1 overdue – friendly nudge, warm tone
- Day 7 – direct ask, invoice re‑attached
- Day 14 – firmer, references previous emails
- Day 30 – final notice before escalation
You set the templates once. The script handles the cadence.
Core logic (invoice_followup.py)
# invoice_followup.py — core logic
import csv, smtplib, datetime
from email.mime.text import MIMEText
STAGES = [
(1, "just checking in", "warm"),
(7, "following up again", "direct"),
(14, "third notice", "firm"),
(30, "final notice", "formal"),
]
def days_overdue(due_date_str):
due = datetime.date.fromisoformat(due_date_str)
return (datetime.date.today() - due).days
def should_send_today(days, last_sent_days):
"""Returns the stage template if today is a follow‑up day."""
for threshold, subject_suffix, tone in STAGES:
if days >= threshold and last_sent_days < threshold:
return subject_suffix, tone
return None, None
def run(invoices_csv, gmail_user, gmail_app_password):
with open(invoices_csv) as f:
rows = list(csv.DictReader(f))
for row in rows:
if row['status'] != 'unpaid':
continue
days = days_overdue(row['due_date'])
last_sent = int(row.get('last_followup_days', 0) or 0)
subject_suffix, tone = should_send_today(days, last_sent)
if not subject_suffix:
continue
body = render_template(tone, row)
send_email(
gmail_user, gmail_app_password,
to=row['client_email'],
subject=f"Invoice {row['invoice_id']} — {subject_suffix}",
body=body
)
row['last_followup_days'] = days
print(f"Sent {tone} follow‑up to {row['client_name']}")
# Write updated CSV back
with open(invoices_csv, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)The render_template function fills in the client name, amount, and due date. The tone shifts from “friendly” to “formal” automatically as the invoice ages.
Setup (≈ 10 minutes)
Export your invoices to CSV (any format — the script can be adapted).
Create a Gmail App Password (2FA required, takes ~2 minutes).
Run once:
python3 invoice_followup.py invoices.csvAdd to cron to run daily:
0 9 * * * python3 /path/to/invoice_followup.py invoices.csv
That’s it. No SaaS, no subscription, no monthly fee—just a script that runs on your machine and sends emails from your own Gmail.
Customization options
- Invoice tool – FreshBooks, Wave, Bonsai, plain CSV, Notion database
- Email provider – Gmail, Outlook, custom SMTP
- Escalation logic – different thresholds, tones, CC a third party at day 30
- WhatsApp or SMS – send via Twilio instead of email
- Slack notification – alert yourself when a follow‑up is sent or a payment arrives
Price: $25 flat. Delivered in 48 hours. One free revision.
If you want the generic version as‑is, it’s free — just ask in the comments and I’ll send the full script.
How to get it
- Free version (generic): Comment below or email citriac@outlook.com — I’ll send the full script.
- Custom version ($25): citriac.github.io/hire — describe your setup; I’ll scope it back within 24 hours.
Related: The Boring Work That Eats Your Freelance Hours — other repetitive things I automate for freelancers.