Skip to content

Goal

Late payments hurt small businesses, and chasing them is awkward, so it gets forgotten. Build a chaser that sends the right reminder at the right time, stops when it should, and tells the owner where the money is.

Prerequisites

  • Modules 05 (Gmail), 13 (static data), 15 (injected clock, per-stage rules), 19 (scheduler instead of Wait nodes)
  • For the live part: a Stripe account (test mode is fine) and Gmail

Concepts

  • No Stripe node for invoices? Use HTTP Request with the Stripe credential. Authentication → Predefined Credential Type → Stripe API signs any Stripe REST call: GET https://api.stripe.com/v1/invoices?status=open&collection_method=send_invoice. Amounts come back in cents, dates as Unix seconds.
  • Stages relative to the due date. Each stage has a day offset (-3, 0, 3, 10, 21, 30). Each run computes days past due in the business's timezone and picks the latest stage that is due and not yet sent.
  • Catch-up without spamming. If the workflow was off for two weeks, a 10-day-late invoice gets only the second reminder, never four emails at once. The skipped stages are reported.
  • A ledger, not a Wait node. Static data records which stage was sent when. That's how the chaser knows not to repeat itself, keeps a 3-day minimum gap, and escalates only 7 days after the final notice.
  • Humans can pause it. A promise to pay pauses until a date; a dispute pauses indefinitely. Nothing is more damaging than an automated "FINAL NOTICE" to a customer who already called you.
  • Tone ladder. Friendly heads-up → due today → "easy to miss" → "when can we expect payment?" → final notice (owner cc'd) → internal escalation to the owner (no customer email). Every email offers a way to reply about problems.
  • Import safely. Turning this on for an existing backlog would email everyone at once. record_only: true marks today's reminders as sent without emailing, so the next run continues from there.

Build it

Step 1: Triggers, Settings, Route

  • Schedule Weekdays 9 AM (cron 0 9 * * 1-5), Webhooks Chase Run API (POST ar/run), Pause API (POST ar/pause), Status API (GET ar/status).
  • Edit Fields Settings (edit me): business and sender name, owner email, currency, timezone, stages, min_gap_days, min_amount, do_not_chase (emails or domains), payment instructions (used when there's no pay link), final-notice note, summary day.
  • Switch Route: run / pause / status.

Step 2: Load invoices

  1. IF Invoices from API? (API call without source: "stripe").
  2. HTTP Request Get Stripe Invoices: GET https://api.stripe.com/v1/invoices, Stripe credential, query status=open, collection_method=send_invoice, limit=100; On Error → Continue.
  3. Code Normalize Invoices (normalize.js): one list with invoice_id, number, customer, email, amount_remaining, currency, due_date, status, pay_url, from the API body (simple or Stripe format) or the Stripe response; a failed fetch becomes source_error.

Step 3: Plan and send

  1. Code Plan Reminders (plan.js): skip rules → days past due (Luxon, timezone) → latest due stage → final-before-escalation → gap check → compose the email. Returns one item per email (and the full plan on the first item).
  2. IF Send Now? (has an invoice, not dry run, not record-only) → Gmail Send Reminder (To, Subject, Text, Cc from the plan), On Error → Continue.
  3. Code Record and Summarize (record.js): a stage is marked sent only when Gmail returned a message id (failures retry next run); aging buckets (current, 1-30, 31-60, 61-90, 90+), largest overdue, summary text.
  4. IF Called via API? → Respond. IF Summary Day? → Gmail Email A/R Summary to the owner.

Step 4: Pause and status

Code Pause Invoice (pause.js) → Respond (400 without an id). Code Invoice Status (status.js) → Respond (404 if unknown).

Publish (static data only persists for production executions).

Test it

./test/test.sh                                           # 60 assertions, no credentials
WITH_CREDS=1 TEST_EMAIL=you@example.com ./test/test.sh   # real reminder email (STRIPE=1 also fetches Stripe)

Preview a run without sending anything:

curl -X POST http://localhost:5678/webhook/ar/run -H 'Content-Type: application/json' -d '{
  "dry_run": true, "now": "2026-10-14T14:00:00Z",
  "invoices": [{"id":"in_1","number":"NW-1001","customer":"Maya Patel","email":"maya@acme.example","amount_remaining":1200,"due_date":"2026-10-04"}]}'

Common problems

  • Every customer emailed on day one: you turned it on over a backlog. Run once with record_only: true first.
  • Reminder a day early or late: check timezone in Settings; due dates are compared in that zone.
  • Stripe returns nothing: invoices charged automatically (charge_automatically) have no due date and are excluded; only send_invoice invoices are chased.
  • More than 100 open invoices: Stripe pages its results (has_more: true in the response). Turn on pagination in the HTTP Request node (Exercise 3).
  • Reminder sent after the customer replied: nothing pauses on replies yet (Exercise 1).