Flow: Schedule Trigger (daily 9am, workflow timezone set) → Google Sheets "Get rows" → Code node (decide) → Send Email (SMTP) → Google Sheets "Update row" (match on row_number).
The decision logic
- Skip rows where status is
paid,cancelledoron hold. -
daysFromDue = today − due_date. Accept bothYYYY-MM-DDandDD/MM/YYYY. - Stages = [−3, 0, 7, 14, 30]. Pick the highest stage whose offset ≤ daysFromDue, so downtime doesn't cause a burst of emails.
- Send only if that stage's index is greater than the stored
last_stage_sentindex (idempotent). - Write back
last_stage_sentandlast_sent_date.
Gotchas we hit
- Send Email v2.1 appends an n8n attribution footer by default. Turn it off under Options.
- Importing with
n8n import:workflowfails if the JSON has noid. - Test against a local SMTP catcher (e.g. Python aiosmtpd) before touching a real inbox.
AU GST wording
If the business is GST-registered: "Tax invoice … Total includes GST of $X", where X = total ÷ 11 rounded to the cent. Otherwise: "Invoice … No GST has been charged".
Packaged version with guide, sample sheet and 5 templates: https://ledgerlinetools.gumroad.com/l/au-invoice-chaser
Published by Ledgerline Tools, a small Australian automation studio. Questions: ledgerlinetools@proton.me
Top comments (1)
Picking the highest due stage is a good rule, because a week of downtime then produces one email instead of three. I see two places where daysFromDue can drift by a day. First, if the Code node builds today from new Date().toISOString(), it gets the UTC date. At 9am in Sydney the UTC date is still yesterday, so the comparison can come out one day short. $today follows the workflow timezone you already set, so it avoids this. Second, depending on its output formatting option, the Google Sheets node can return dates as serial numbers instead of text. A five-digit number matches neither YYYY-MM-DD nor DD/MM/YYYY, so a column that someone reformats could quietly drop out of the reminders. Setting that option to formatted values, or handling the number explicitly, prevents that. The order of steps matters too: the email goes out before last_stage_sent is written, so if Update row fails, the next run sends the same stage again. Writing a sending marker first, or at least alerting when the update fails, closes that gap.