Last week, a CA firm in Jaipur reached out to me with a painfully familiar problem: their accountant was manually creating invoices in Excel, converting them to PDF, and sending them on WhatsApp — one by one — to 200+ clients every month.
Time spent? About 3 full working days. Every. Single. Month.
I built them an automated system in 3 hours flat. Here's exactly how.
The Problem
The firm's workflow looked like this:
- Open Excel template
- Fill in client name, GSTIN, amounts, due dates
- Save as PDF
- Open WhatsApp Web
- Search for the client
- Attach PDF, type a message, hit send
- Repeat 200 times
The accountant was burning ~24 hours/month on this. At ₹300/hour, that's ₹7,200/month — or ₹86,400/year — just on invoice delivery.
The Solution: Python + WhatsApp Business API
I broke the automation into 3 parts:
- Invoice generation from a Google Sheet
- PDF creation using a template
- WhatsApp delivery via the Business API
Part 1: Pull Data from Google Sheets
First, I set up a Google Sheet as the single source of truth. The accountant just fills in the data — the script does the rest.
import gspread
from google.oauth2.service_account import Credentials
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
creds = Credentials.from_service_account_file('creds.json', scopes=SCOPES)
client = gspread.authorize(creds)
sheet = client.open("Monthly_Invoices").worksheet("April2026")
records = sheet.get_all_records()
# Each record has: client_name, gstin, amount, due_date, phone
print(f"Found {len(records)} invoices to process")
This gives us a clean list of dictionaries — one per client.
Part 2: Generate PDF Invoices
I used reportlab for PDF generation. The firm wanted their letterhead, so I created a reusable template.
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
from datetime import datetime
import os
def generate_invoice(data, output_dir="invoices"):
os.makedirs(output_dir, exist_ok=True)
filename = f"{output_dir}/{data['client_name']}_{datetime.now().strftime('%B_%Y')}.pdf"
c = canvas.Canvas(filename, pagesize=A4)
width, height = A4
# Header
c.setFont("Helvetica-Bold", 18)
c.drawString(1*inch, height - 1*inch, "TAX INVOICE")
# Firm details
c.setFont("Helvetica", 10)
c.drawString(1*inch, height - 1.5*inch, "Sharma & Associates, Chartered Accountants")
c.drawString(1*inch, height - 1.7*inch, "GSTIN: 08AXXXX1234X1Z5")
# Client details
c.setFont("Helvetica-Bold", 11)
c.drawString(1*inch, height - 2.5*inch, f"Bill To: {data['client_name']}")
c.setFont("Helvetica", 10)
c.drawString(1*inch, height - 2.8*inch, f"GSTIN: {data['gstin']}")
# Invoice details
inv_number = f"INV-{datetime.now().strftime('%Y%m')}-{data['client_name'][:3].upper()}"
c.drawString(4.5*inch, height - 2.5*inch, f"Invoice #: {inv_number}")
c.drawString(4.5*inch, height - 2.8*inch, f"Date: {datetime.now().strftime('%d-%m-%Y')}")
c.drawString(4.5*inch, height - 3.1*inch, f"Due Date: {data['due_date']}")
# Amount table
amount = float(data['amount'])
gst = amount * 0.18
total = amount + gst
y_pos = height - 4*inch
c.setFont("Helvetica-Bold", 10)
c.drawString(1*inch, y_pos, "Description")
c.drawString(5*inch, y_pos, "Amount (₹)")
c.setFont("Helvetica", 10)
c.drawString(1*inch, y_pos - 0.4*inch, "Professional Services")
c.drawString(5*inch, y_pos - 0.4*inch, f"{amount:,.2f}")
c.drawString(1*inch, y_pos - 0.7*inch, "GST @ 18%")
c.drawString(5*inch, y_pos - 0.7*inch, f"{gst:,.2f}")
c.setFont("Helvetica-Bold", 12)
c.drawString(1*inch, y_pos - 1.2*inch, "TOTAL")
c.drawString(5*inch, y_pos - 1.2*inch, f"₹{total:,.2f}")
# Bank details
c.setFont("Helvetica", 9)
c.drawString(1*inch, 2*inch, "Bank: HDFC Bank | A/C: XXXX1234 | IFSC: HDFC0001234")
c.save()
return filename
# Generate all invoices
invoice_files = {}
for record in records:
path = generate_invoice(record)
invoice_files[record['phone']] = {
'path': path,
'name': record['client_name'],
'amount': record['amount']
}
print(f"Generated invoice for {record['client_name']}")
Part 3: Send via WhatsApp Business API
This is where the magic happens. I used the WhatsApp Business API (via a BSP provider — costs about ₹0.50 per message in India).
import requests
import time
WHATSAPP_TOKEN = "your_access_token"
PHONE_NUMBER_ID = "your_phone_number_id"
def upload_media(file_path):
"""Upload PDF to WhatsApp media endpoint"""
url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/media"
headers = {"Authorization": f"Bearer {WHATSAPP_TOKEN}"}
with open(file_path, 'rb') as f:
files = {'file': (file_path, f, 'application/pdf')}
data = {'messaging_product': 'whatsapp'}
response = requests.post(url, headers=headers, files=files, data=data)
return response.json().get('id')
def send_invoice(phone, media_id, client_name, amount):
"""Send invoice PDF with a message via WhatsApp"""
url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages"
headers = {
"Authorization": f"Bearer {WHATSAPP_TOKEN}",
"Content-Type": "application/json"
}
payload = {
"messaging_product": "whatsapp",
"to": f"91{phone}",
"type": "document",
"document": {
"id": media_id,
"filename": f"Invoice_{client_name}.pdf",
"caption": (
f"Hello {client_name},\n\n"
f"Please find attached your invoice for the amount "
f"of Rs.{float(amount):,.2f} (inclusive of GST).\n\n"
f"Kindly process the payment by the due date.\n\n"
f"Regards,\nSharma & Associates"
)
}
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
# Send all invoices
sent_count = 0
failed = []
for phone, info in invoice_files.items():
try:
media_id = upload_media(info['path'])
result = send_invoice(phone, media_id, info['name'], info['amount'])
if 'messages' in result:
sent_count += 1
print(f"Sent to {info['name']} ({phone})")
else:
failed.append(info['name'])
print(f"Failed for {info['name']}: {result}")
time.sleep(1) # Rate limiting
except Exception as e:
failed.append(info['name'])
print(f"Error for {info['name']}: {e}")
print(f"\nDone! Sent: {sent_count} | Failed: {len(failed)}")
if failed:
print(f"Failed clients: {', '.join(failed)}")
Putting It All Together
I wrapped everything in a single run.py script with logging and error handling. The accountant now:
- Updates the Google Sheet with this month's data
- Runs
python run.py(or I set up a cron job for the 1st of every month) - Gets a summary in the terminal showing what was sent
Total execution time for 200 invoices: under 8 minutes.
The Numbers
| Metric | Before | After |
|---|---|---|
| Time per month | ~24 hours | ~8 minutes |
| Cost per month | ₹7,200 (labor) | ₹100 (API costs) |
| Errors (wrong client) | 2-3/month | 0 |
| Client complaints | Frequent delays | Same-day delivery |
Annual savings: ₹85,200 in labor + eliminated errors.
The firm paid me ₹15,000 for this build — ROI in less than 2 months.
Key Lessons
Start with the spreadsheet. Every automation project I take on starts with understanding the data source. If the data is messy, fix that first. The actual automation is the easy part.
Use APIs, not UI bots. I could have used browser automation to control WhatsApp Web, but that breaks every time WhatsApp updates their UI. The Business API is stable, fast, and gives you delivery receipts.
Build for the person who runs it. The accountant isn't a developer. So I made the Google Sheet the only interface they touch. No terminal, no config files, no "just edit this JSON."
Charge for value, not hours. This took me 3 hours. If I charged hourly at ₹1,000/hr, that's ₹3,000. But I saved them ₹85,000/year. ₹15,000 is a no-brainer for the client and fair compensation for the value delivered.
Want to Build Something Similar?
Here's the minimal setup you need:
- Python 3.8+
-
gspreadandgoogle-authfor Sheets access -
reportlabfor PDF generation - WhatsApp Business API access (apply through Meta Business)
- A Google Cloud service account with Sheets API enabled
Install everything with:
pip install gspread google-auth reportlab requests
The entire codebase is under 200 lines. You could build this in an afternoon.
I'm Archit Mittal — I automate chaos for businesses. Follow me for daily automation content.
Top comments (0)