Part 4 of 7 · Invoice chaser series ~5 min read

How a reminder reaches the customer

The chaser picked a move — first nudge, follow-up, or escalate. Now the sender Lambda has to figure out who to email, in what tone, at what time of day, and with what attached. Get any of those wrong and the reminder is worse than no reminder: a Saturday email, a blunt “you owe us money,” a reminder to a contact who left the customer last year. Four small guardrails sit between the move and the actual send.

Key takeaways

  • Contact resolution: per-invoice override beats per-customer default beats fallback to the account owner.
  • The tone ladder climbs from friendly to firm — the right template for the step, never rude.
  • Quiet hours, weekends, and holidays defer a send to the next available business hour.
  • Every reminder ships with the invoice number, amount, days past due, a pay-link, and the PDF.
  • Escalation goes to the account owner with the full history; the customer isn’t auto-emailed again.

Four guardrails on every send

Four guardrails between the chaser's chosen move and the sent reminder A horizontal flow diagram. On the far left, a "Move chosen" box: the chaser emitted an event with the invoice, the terms, and one of three sending moves — first nudge, follow-up, or escalate. Four guardrail gates sit in a row to the right, each drawn as a vertical bar. Gate 1: Resolve contact — looks up the per-invoice contact column first; if blank, falls back to the per-customer billing contact from the rules doc; if still blank, falls back to the account owner. Returns a billing email; for an escalate move it returns the internal account owner's email instead. Gate 2: Quiet hours and weekends — checks the local time against the rules-doc quiet-hours window (default 6pm to 8am) and skips Saturdays and Sundays; if outside business hours, defers the send to the next available business minute via an EventBridge Scheduler one-off rule. Gate 3: Holiday calendar — checks the date against the configured holiday list; if it's a holiday, defers to the next non-holiday business day. Gate 4: Compose on the tone ladder — picks the voice-doc template for the chosen move (friendly for the nudge, firmer for the follow-up, final notice for escalate), fills in the invoice number, amount due, days past due, pay-link, and a link to the PDF, and keeps the tone polite no matter the step. After all four gates pass, the reminder ships via SES outbound to the resolved email. A note at the bottom: every send is logged to DynamoDB so the next tick knows the reminder has fired. Move chosen first nudge, follow-up, or escalate Gate 1 Resolve contact invoice override? customer default? owner fallback? billing email, owner if escalate Gate 2 Quiet hrs + weekend local time in business window? if not, defer via Scheduler Gate 3 Holiday calendar date in holiday list? if yes, defer to next business day Gate 4 Compose on ladder tone template for the step + number, amount, days, pay-link, PDF link Send — SES outbound email to the contact (owner on escalate) SES SendRawEmail · pay-link Function URL · replies to inbox every send logged to DDB ic-sends — the next tick won’t duplicate Every gate is a deterministic check — no model calls, no rude surprise on a Saturday in April.
Fig 4. Four guardrails between the move and the sent reminder. Resolve the contact. Honor quiet hours and weekends. Skip holidays. Compose on the tone ladder with full context. Then send via SES and log it so the next tick doesn’t duplicate.

Gate 1: resolve the contact

Three places the sender Lambda looks for who to email, in order. First, the invoice sheet’s per-invoice contact_email column — if a row has a specific billing contact, that person gets the reminder regardless of the customer default. Second, the per-customer default in the rules doc (“all Acme Co. invoices go to ap@acme.com”). Third, the account owner fallback — the internal person who owns the relationship and gets every reminder that has no customer contact. The fallback should never fire in steady state; if it does, the weekly digest names every invoice that hit the fallback so the rules doc can be updated.

For an escalate move, the resolution flips: the reminder goes to the internal account owner, not the customer. That’s the whole point of escalation — once an invoice has been chased to the end of its cadence with no payment, a person should take over the conversation, not the system.

Gate 2: quiet hours and weekends

The chaser itself runs at 9am local time, so the first time a move fires it’s already in business hours on a weekday. But deferred sends from previous gates, and one-off computed sends, can land outside the window. Gate 2 reads the rules doc’s quiet-hours setting (default 6pm to 8am, configurable per business) and the weekend rule (skip Saturday and Sunday by default).

If the current local time is in the quiet window or it’s a weekend, the sender creates a one-off EventBridge Scheduler rule that fires at the next business-hour minute and exits without sending. The Scheduler invokes the same sender Lambda with the same payload at the deferred time, where Gate 2 will let it through. A payment reminder that lands at 2am Sunday reads as careless; one that lands at 9am Monday reads as a business doing its job.

Gate 3: holiday calendar

The rules doc lists the holidays you observe — either a static list (“Christmas Day, New Year’s Day, Independence Day...”) or a reference to a Google Calendar that holds them. Gate 3 checks the current local date against that list and, if it’s a configured holiday, defers the send to the next non-holiday business day.

The list is on purpose — the chaser won’t auto-detect a country’s public holidays for you. The failure modes are very different. A holiday you forgot to add sends a reminder into a closed office — harmless but pointless. A holiday in the list that’s no longer observed just delays a reminder by one business day, which is fine. The trade-off favors keeping the list explicit.

Gate 4: compose on the tone ladder, then send

The voice doc has one email template per step of the tone ladder. The first nudge is warm and short: “Just a friendly reminder that invoice #1042 for $6,400 was due on May 1 — here’s a link to pay, and the PDF if you need it.” The follow-up is firmer but still courteous, and names the date of the previous reminder. The escalate notice (which goes to the internal owner, not the customer) summarizes the history so a human can pick up the phone. Every template carries the invoice number, amount due, days past due, a pay-link, and a link to the PDF. The sender fills the placeholders and ships the message via SES outbound.

The pay-link is a Function URL the customer can click to settle the invoice — it hands off to whatever payment surface your accounting tool exposes, and records that the link was opened so the chaser knows the customer engaged. The reply-to address is your real inbox, so if the customer writes back “already paid” or “can we split this,” a human sees it.

An escalate move doesn’t send the customer yet another email — the cadence is done, and piling on more automated reminders past that point reads as harassment, not diligence. Instead it hands the account owner the full picture: every reminder that went out, the dates, and the cumulative days overdue. From there it’s a person’s call.

Every send — nudge, follow-up, or escalation — writes a row to ic-sends in DynamoDB. The next day’s tick reads that row and knows not to send the same step again.

Why the guardrails exist

None of these gates are exotic. They’re the kind of small care a thoughtful person would take if they were chasing the invoices themselves — check who actually handles billing, don’t email at 11pm or on a Sunday, skip the day everyone’s off, stay polite even when an invoice is very late, and hand the awkward ones to a human. Putting them in code as four small sequential gates makes them part of the design, not a feature you’re trusting the writer of any one reminder to remember.

Next post: how a chase stops the moment payment lands — and the three things the owner can do when an invoice needs a human: pause, mark disputed, or write off.

All posts