The pattern: a sweep, not a timer per item
Don’t create one delayed job per invoice. Run a short schedule that finds whatever is due now, handles it, and marks it. A missed run just means the next run finds a bit more; nothing is lost if a server restarts.1. The table
invoices with: customer_email, amount, due_at (datetime), status (open, paid), and reminded_at (datetime, empty by default). Add an index on (status, due_at) so the sweep is fast as the table grows (Indexes).
2. The template
Mail → Templates:invoice-due from the Invoice design, values invoice.number, invoice.amount, invoice.due_at, pay_url. Publish it.
3. The flow
Flows → New flowinvoice-reminders, with a Manual run trigger (the schedule starts it):
-
SQL query, node id
due: find what’s due and not reminded. Using SQL lets you compare dates:with params["{{ input.scheduled_at }}"]. -
For each over
{{ steps.due.output }}. Inside itseachbranch, the current row isitem(and its positionindex):- Send email: To
{{ item.customer_email }}, Templateinvoice-due, Data{"invoice": "{{ item }}", "pay_url": "https://app.example.com/pay/{{ item.id }}"}. - Update record: table
invoices, id{{ item.id }}, data{"reminded_at": "{{ input.scheduled_at }}"}.
- Send email: To
-
A Log block after the loop’s
doneexit:Reminded {{ steps.due.output | length }} invoices.
LIMIT 200 keeps each run well inside the flow’s time limit. If more are due, the next run picks them up. For bigger volumes, run more often (hourly) rather than raising the limit.4. The schedule
Schedules → New schedule:
The flow receives
{ "scheduled_at": "…" }. Using scheduled_at instead of “now” means a late run still uses the time it was meant for.
5. Test it
- Create two invoices in
development: one due yesterday, one due next week. - On the schedule, press Run now.
- Activity → Flow runs: open the run. The
duestep should have one row, the loop one iteration. - Mail activity: one
invoice-duemessage. - Run now again: the
duestep is now empty, becausereminded_atis set. That’s the double-send protection working.
6. Make failures loud
- An alarm on Failed flow runs above 0 over 60 minutes, emailing your team.
- Activity → Overview flags Schedule invoice-reminders failed on its last run.
reminded_at set and the rest don’t, so re-running is safe: press Run now after fixing the cause.
Variations
Schedules
Every schedule setting.
Background jobs
Retries and replay.