DEV Community

Ledgerline Tools
Ledgerline Tools

Posted on

Build an invoice reminder workflow in n8n with Google Sheets (idempotent, AU GST wording)

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

  1. Skip rows where status is paid, cancelled or on hold.
  2. daysFromDue = today − due_date. Accept both YYYY-MM-DD and DD/MM/YYYY.
  3. Stages = [−3, 0, 7, 14, 30]. Pick the highest stage whose offset ≤ daysFromDue, so downtime doesn't cause a burst of emails.
  4. Send only if that stage's index is greater than the stored last_stage_sent index (idempotent).
  5. Write back last_stage_sent and last_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:workflow fails if the JSON has no id.
  • 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)

Collapse
 
hayrullahkar profile image
Hayrullah Kar •

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.