Automated Invoice Payment Reminders
Chasing unpaid invoices manually is time-consuming. This script checks for overdue invoices daily and sends reminders via email or Slack.
Daily Reminder Script
python
#!/usr/bin/env python3
# invoice-reminders.py
import subprocess, json, smtplib, datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Config
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
SMTP_USER = "billing@yourcompany.com"
SMTP_PASS = "your-app-password"
FROM_NAME = "Your Company Billing"
def ao(cmd):
r = subprocess.run(["ao"] + cmd.split() + ["--json"], capture_output=True, text=True)
return json.loads(r.stdout).get("data", [])
def send_email(to: str, subject: str, body: str):
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = f"{FROM_NAME} <{SMTP_USER}>"
msg["To"] = to
msg.attach(MIMEText(body, "html"))
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as s:
s.starttls()
s.login(SMTP_USER, SMTP_PASS)
s.sendmail(SMTP_USER, to, msg.as_string())
overdue = ao("invoice list --status overdue --limit 100")
today = datetime.date.today()
print(f"Found {len(overdue)} overdue invoices")
for inv in overdue:
inv_num = inv.get("invoice_number", inv["id"][:8])
amount = inv.get("total_amount") or inv.get("amount") or 0
due_date = inv.get("due_date", "")
contact = inv.get("contact_email") or inv.get("customer_email", "")
if not contact:
print(f" SKIP {inv_num}: no contact email")
continue
days_overdue = (today - datetime.date.fromisoformat(due_date)).days if due_date else 0
subject = f"Payment Reminder: Invoice #{inv_num} is {days_overdue} days overdue"
body = f"""
<p>Dear Valued Client,</p>
<p>This is a friendly reminder that invoice <strong>#{inv_num}</strong>
for <strong>${amount:,.2f}</strong> was due on <strong>{due_date}</strong>
and is now <strong>{days_overdue} days overdue</strong>.</p>
<p>Please arrange payment at your earliest convenience.</p>
<p>If you have already paid, please disregard this notice.</p>
<p>Best regards,<br>{FROM_NAME}</p>
"""
send_email(contact, subject, body)
print(f" Sent reminder for #{inv_num} to {contact}")
Slack Version
python
import urllib.request
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK"
def slack_notify(text: str):
data = json.dumps({"text": text}).encode()
req = urllib.request.Request(SLACK_WEBHOOK, data=data,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req)
total = sum(i.get("total_amount") or 0 for i in overdue)
slack_notify(f":rotating_light: *{len(overdue)} overdue invoices* totalling *${total:,.0f}*\nRun `ao invoice list --status overdue` for details.")
Cron Schedule
bash
# Monday to Friday at 9 AM
0 9 * * 1-5 python3 /path/to/invoice-reminders.py >> /var/log/reminders.log 2>&1
Mark Batch Paid (after bank reconciliation)
bash
ao invoice list --status overdue --json \
| jq -r ".data[].id" \
| while read -r ID; do
ao invoice mark-paid "$ID"
echo "Marked paid: $ID"
done
Tip: Check the days_overdue threshold before sending — you may want to only remind after 7+ days to avoid annoying clients with small delays.