Because manually texting 20 people every month is not a vibe.
The Problem
My friend manage a shared Netflix account for a bunch of people. Every month, he have to manually message each person reminding them to pay. Sometimes he forget. Sometimes they'd forget. It was chaos.
So I did what any developer would do to help him. I spent time automating it instead of just doing it manually. Worth it.
What I Wanted to Build
A system that:
- Reads subscriber data from Google Sheets
- Automatically calculates when each person's payment is due
- Sends WhatsApp reminders at H-3, H-1, due date, and H+1 (overdue)
- Costs as close to $0 as possible
- Runs on my homeserver with zero manual intervention
The Stack (and Why)
Here's what I ended up with:
- Google Sheets, already where I track everything. No new tools needed.
- Google Apps Script, free, runs in the cloud, can hit external APIs. Perfect for the automation logic.
- Go WhatsApp Web Multidevice (GoWA), a lightweight Go-based self-hosted WhatsApp API. No monthly fees, no third-party services, runs on my homeserver with ~50MB RAM.
- Cloudflare Tunnel, exposes my homeserver to the internet for free, no port forwarding needed.
Setting Up GoWA
GoWA is stupid simple to get running. One command:
1wget https://github.com/aldinokemal/go-whatsapp-web-multidevice/releases/latest/download/whatsapp-linux-amd64 -O whatsapp && \
2chmod +x whatsapp && \
3sudo mv whatsapp /usr/local/bin/gowa && \
4sudo bash -c 'cat > /etc/systemd/system/gowa.service << EOF
5[Unit]
6Description=Go WhatsApp Web
7After=network.target
8
9[Service]
10ExecStart=/usr/local/bin/gowa --port 3001
11Restart=always
12User='$USER'
13
14[Install]
15WantedBy=multi-user.target
16EOF' && \
17sudo systemctl daemon-reload && \
18sudo systemctl enable gowa && \
19sudo systemctl start gowaThen open http://localhost:3001, scan the QR code, and you're connected.

Exposing the Homeserver with Cloudflare Tunnel
Google Apps Script runs on Google's servers — it can't reach localhost. So I used Cloudflare Tunnel to give GoWA a public HTTPS URL for free.
1cloudflared tunnel login
2cloudflared tunnel create gowa
3cloudflared tunnel route dns gowa wa.yourdomain.com
4cloudflared tunnel run gowaNow GoWA is accessible at https://wa.yourdomain.com from anywhere.
Google Sheets Structure
Simple. Four columns:
1Nama No WA Tgl Bayar Terakhir Status
2Budi 62812xxx 01/03/2026 AktifThe Status column is auto-calculated:
1=IF(ISBLANK(C2),"-",IF(TODAY()-C2<30,"Aktif","Nonaktif"))No manual status updates needed — it flips automatically based on the last payment date.

The Brain: Google Apps Script
This is where the magic happens. The script:
1. Reads all rows from the sheet
2. Calculates the next billing date (last payment + 30 days)
3. Checks if today is H-3, H-1, due date, or H+1
4. Sends a WhatsApp message via GoWA if it matches
1const CONFIG = {
2 GOWA_URL: 'https://wa.yourdomain.com',
3 GOWA_USER: 'admin',
4 GOWA_PASS: 'your-password',
5 DEVICE_ID: 'default',
6 SHEET_NAME: 'Sheet1',
7 REMINDER_DAYS: [3, 1, 0, -1]
8}
9
10function cekTagihan() {
11 const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME)
12 const data = sheet.getDataRange().getValues()
13 const today = new Date()
14 today.setHours(0, 0, 0, 0)
15
16 for (let i = 1; i < data.length; i++) {
17 const nama = data[i][0]
18 const noWa = data[i][1].toString()
19 const tglBayarRaw = data[i][2]
20 const status = data[i][3]
21
22 if (status !== 'Aktif') continue
23
24 const tglBayar = new Date(tglBayarRaw)
25 if (isNaN(tglBayar.getTime())) continue
26
27 tglBayar.setHours(0, 0, 0, 0)
28 const tglTagihan = new Date(tglBayar)
29 tglTagihan.setDate(tglTagihan.getDate() + 30)
30
31 const selisih = Math.round((tglTagihan - today) / (1000 * 60 * 60 * 24))
32
33 if (!CONFIG.REMINDER_DAYS.includes(selisih)) continue
34
35 let tipe
36 if (selisih === 3) tipe = 'H-3'
37 else if (selisih === 1) tipe = 'H-1'
38 else if (selisih === 0) tipe = 'Hari-H'
39 else if (selisih === -1) tipe = 'Telat'
40
41 const pesan = formatPesan(nama, tipe, tglTagihan)
42 kirimWA(noWa, pesan)
43 Utilities.sleep(1000)
44 }
45}
The WhatsApp Messages
Each reminder type has its own message template:
H-3:
Halo Budi! 👋
Reminder Netflix kamu akan jatuh tempo 3 hari lagi (04/04/2026).
Jangan lupa perpanjang ya! 🙏
Overdue:
Halo Budi! 🔴
Tagihan Netflix kamu sudah melewati jatuh tempo (04/04/2026).
Hubungi admin untuk perpanjang.

Setting the Trigger
In Apps Script → Triggers → Add Trigger:
- Function: cekTagihan
- Event: Time-driven → Day timer → 8am–9am
GAS doesn't support exact-time triggers, but an 8–9am window is good enough for payment reminders.

Final Architecture
Google Sheets (subscriber data) → Google Apps Script (runs daily 8–9am) → HTTPS Cloudflare Tunnel → GoWA on Homeserver (port 3001) → WhatsApp
Total cost: $0/month (assuming you already have a homeserver).
What I'd Improve Next
- Add a "Sudah Bayar" button that auto-updates the last payment date
- Track payment history per subscriber
- Send reminders via Telegram as a fallback
- Move config to a dedicated Settings sheet so non-devs can update it
Wrapping Up
This took a few hours to build but saves my friends from manual messaging every single month. The stack is boring in the best way. No paid services, no complex infra, just a few free tools stitched together.
If you're managing a shared account or running a small subscription business, this approach works surprisingly well.