The Invoice Problem Every Freelancer Knows
You delivered the work. You sent the invoice. Then... silence. You wait a week, send a polite WhatsApp reminder. Another week, a follow-up email. Month-end, you're still chasing ₦350,000 across 4 clients.
Late payments aren't just annoying — they're a cash flow killer. A 2024 survey of Nigerian freelancers found that 68% experience payment delays averaging 23 days past due.
Here's the fix: automate the entire invoicing lifecycle with Google Sheets and Apps Script. Zero cost. Zero server. Zero excuses.
What This System Does
| Step | Manual Process | Automated System |
|---|---|---|
| Create invoice | 15 min in Word/Canva | 2 sec from template |
| Send to client | Manual email | Auto-email as PDF |
| Track payment | Check bank, update sheet | Auto-update from Paystack |
| Send reminder | WhatsApp, pray | Auto-reminder at 7, 14, 21 days |
| Mark overdue | Highlight manually | Color-coded auto-status |
Tool Comparison: Invoicing for African Businesses
| Tool | Free? | Auto-Reminders | Paystack Integration | Monthly Cost |
|---|---|---|---|---|
| Google Sheets + Script | Yes | Yes (custom) | Yes (API) | ₦0 |
| Wave Apps | Yes | Yes | No | ₦0 |
| Zoho Invoice | Yes (up to 10) | Yes | No | ₦0–₦5,400 |
| FreshBooks | No | Yes | No | ₦18,000+ |
| Bonsai | No | Yes | No | ₦13,500+ |
| QuickBooks | No | Yes | No | ₦20,000+ |
The Google Sheets approach wins because it's free, fully customisable, and integrates with Paystack — the payment gateway that actually works in Nigeria and Ghana.
Step 1: Set Up Your Invoice Sheet
Create a Google Sheet with these columns:
| Column | Header | Example |
|---|---|---|
| A | Invoice # | INV-2026-001 |
| B | Client Name | TechCo Lagos |
| C | Client Email | pay@techco.ng |
| D | Amount (₦) | 350000 |
| E | Issue Date | 2026-03-01 |
| F | Due Date | 2026-03-15 |
| G | Status | Pending |
| H | Days Overdue | 0 |
| I | Reminder Count | 0 |
| J | Payment Ref | |
| K | Notes | Web redesign project |
Step 2: Auto-Generate Invoice PDFs
Open Extensions → Apps Script and add this:
function generateInvoicePDF(row) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Invoices');
const data = sheet.getRange(row, 1, 1, 11).getValues()[0];
const [invoiceNo, client, email, amount, issueDate, dueDate] = data;
// Create invoice document from template
const templateId = 'YOUR_TEMPLATE_DOC_ID';
const docCopy = DriveApp.getFileById(templateId).makeCopy();
const doc = DocumentApp.openById(docCopy.getId());
const body = doc.getBody();
body.replaceText('{{invoice_no}}', invoiceNo);
body.replaceText('{{client_name}}', client);
body.replaceText('{{amount}}', '₦' + Number(amount).toLocaleString());
body.replaceText('{{issue_date}}', issueDate);
body.replaceText('{{due_date}}', dueDate);
doc.saveAndClose();
// Export as PDF
const pdfBlob = docCopy.getAs(MimeType.PDF);
pdfBlob.setName(invoiceNo + '.pdf');
// Send via email
MailApp.sendEmail({
to: email,
subject: 'Invoice ' + invoiceNo + ' from Your Business Name',
body: 'Hi ' + client + ',\n\nPlease find attached invoice ' + invoiceNo + ' for ₦' + Number(amount).toLocaleString() + '.\n\nPayment is due by ' + dueDate + '.\n\nThank you for your business.',
attachments: [pdfBlob]
});
// Clean up temp doc
DriveApp.getFileById(docCopy.getId()).setTrashed(true);
sheet.getRange(row, 7).setValue('Sent');
}
Step 3: Auto-Calculate Overdue Status
Add a daily trigger that updates overdue counts:
function updateOverdueStatus() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Invoices');
const lastRow = sheet.getLastRow();
const today = new Date();
for (let row = 2; row <= lastRow; row++) {
const status = sheet.getRange(row, 7).getValue();
const dueDate = new Date(sheet.getRange(row, 6).getValue());
if (status === 'Paid') continue;
const daysOverdue = Math.floor((today - dueDate) / (1000 * 60 * 60 * 24));
sheet.getRange(row, 8).setValue(Math.max(0, daysOverdue));
// Update status and color
if (daysOverdue > 21) {
sheet.getRange(row, 7).setValue('Overdue');
sheet.getRange(row, 7).setBackground('#ff4444');
} else if (daysOverdue > 0) {
sheet.getRange(row, 7).setValue('Late');
sheet.getRange(row, 7).setBackground('#ffaa00');
}
}
}
Step 4: Automated Payment Reminders
function sendReminders() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Invoices');
const lastRow = sheet.getLastRow();
for (let row = 2; row <= lastRow; row++) {
const status = sheet.getRange(row, 7).getValue();
const daysOverdue = sheet.getRange(row, 8).getValue();
const reminderCount = sheet.getRange(row, 9).getValue();
const client = sheet.getRange(row, 2).getValue();
const email = sheet.getRange(row, 3).getValue();
const amount = sheet.getRange(row, 4).getValue();
const invoiceNo = sheet.getRange(row, 1).getValue();
// Send at 7, 14, and 21 days overdue
const shouldRemind = [7, 14, 21].includes(daysOverdue) && reminderCount < 3;
if (status !== 'Paid' && shouldRemind) {
const tone = daysOverdue >= 21 ? 'firm' : daysOverdue >= 14 ? 'direct' : 'friendly';
const subjects = {
friendly: 'Friendly Reminder: Invoice ' + invoiceNo,
direct: 'Follow-Up: Invoice ' + invoiceNo + ' is ' + daysOverdue + ' days overdue',
firm: 'URGENT: Invoice ' + invoiceNo + ' — Immediate Payment Required'
};
MailApp.sendEmail({
to: email,
subject: subjects[tone],
body: 'Hi ' + client + ',\n\nThis is a ' + tone + ' reminder that invoice ' + invoiceNo + ' for ₦' + Number(amount).toLocaleString() + ' is ' + daysOverdue + ' days overdue.\n\nPlease process payment at your earliest convenience.'
});
sheet.getRange(row, 9).setValue(reminderCount + 1);
}
}
}
Step 5: Set Up the Triggers
In Apps Script, go to Triggers (clock icon) and add:
| Function | Trigger Type | Frequency |
|---|---|---|
| updateOverdueStatus | Time-driven | Daily, 8:00am |
| sendReminders | Time-driven | Daily, 9:00am |
Common Gotchas
| Issue | Cause | Fix |
|---|---|---|
| Emails not sending | Gmail daily limit (100/day) | Stay under 80; use Mailgun for volume |
| PDF template not found | Wrong Doc ID | Copy ID from the URL of your template |
| Dates showing as numbers | Sheets serial number format | Use `new Date()` conversion |
| Reminders going to paid clients | Status not updated | Mark "Paid" immediately on payment |
| Paystack webhook not working | Wrong sheet name in script | Match the sheet name exactly |
Real Impact
A freelance designer in Lagos implemented this system and tracked the results over 3 months:
- Average payment time: dropped from 23 days to 9 days
- Overdue invoices: reduced by 74%
- Time on invoice admin: from 4 hours/week to 15 minutes/week
- Cost: ₦0
The system works because it removes the awkwardness of manual follow-ups. Clients receive professional, escalating reminders automatically. No emotions. No forgetting. No lost invoices.