Part 4 of 7 · Tax doc collector series ~5 min read

How a client gets chased for missing docs

Once a day, at 8am local time, an EventBridge Scheduler rule fires the tracker Lambda. It reads each client file, sees what’s still missing and how many days since the first request, and picks one of four moves: do nothing, send the first request, send a reminder, or escalate to the preparer. When a move means sending something, four small guardrails decide who gets it, on what day, and with what wording — so a client never gets a 2am email or a vague “please send your documents.” The whole decision is plain Python. No model on the tick.

Key takeaways

  • The tracker runs once a day via EventBridge Scheduler at 8am local time.
  • The chase cadence lives in the rules doc — first request on setup, then reminders at day 7, 14, and 21.
  • Four moves per file, every tick: complete, first request, reminder, escalate.
  • Reminders list only what’s still missing — a client who sent three of four sees only the one that’s left.
  • Four guardrails on every send: resolve the contact, honor quiet hours, skip holidays, compose with the exact list.

Four guardrails on every send

Four guardrails between the chosen move and the sent request A horizontal flow diagram. On the far left, a "Move chosen" box: the tracker picked one of three sending moves for a file — first request, reminder, 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 client's contact email; if the client added a spouse or business partner as a handoff, includes them; if no contact is set, falls back to flagging the file for the preparer. Gate 2: Quiet hours — checks the local time against the rules-doc quiet-hours window (default 6pm to 8am); 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: Final compose — fills the request or reminder template from the voice doc for the chosen move, lists only the checklist items still missing, attaches a fresh signed upload link, and, on escalate, addresses the preparer instead of the client. After all four gates pass, the request ships via SES outbound to the client, or the file is flagged on the preparer's status board for an escalate. A note at the bottom: every send is logged to DynamoDB so the next tick knows the request has gone out. Move chosen first request, reminder, or escalate Gate 1 Resolve contact client email? handoff added? none → preparer include spouse or partner if set Gate 2 Quiet hours 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 + the list voice template for the move + missing items, upload link, due date Send — SES email to the client, or flag on the preparer's board (escalate) SES SendRawEmail with a fresh signed upload link every send logged to DDB td-sends — the next tick won’t duplicate Every gate is a deterministic check — no model calls, no surprise behavior on a Tuesday in February.
Fig 4. Four guardrails between the move and the sent request. Resolve the contact. Honor quiet hours. Skip holidays. Compose with only the missing items and a fresh upload link. Then ship via email and log the send so the next tick doesn’t duplicate.

The four moves, per file, per tick

Every file, every tick, lands in exactly one of four buckets. The names are simple on purpose.

  • Complete. Every checklist item is received. The tracker marks the file ready for review and tells the preparer (Part 5). It stops chasing the client. Most files, once finished, sit here.
  • First request. The file was just set up and no request has gone out. Send the full list with the upload link. Write a row to the td-sends DynamoDB table marking that the first request fired.
  • Reminder. A reminder day in the cadence has passed and items are still missing. Send a follow-up that lists only what’s left, not the whole checklist again. The client who sent three of four documents sees a short note about the one that’s missing. Write the new send to td-sends.
  • Escalate. The due date has passed and items are still missing. Don’t keep emailing the client into the void — instead, flag the file on the preparer’s status board so a person can pick up the phone. The client may still get a final reminder, but the human now owns it. Mark the file escalated in DynamoDB.

The cadence: day 7, 14, 21 isn’t magic, it’s in the doc

The rules doc has one short section per client type. Each names the cadence in plain prose: “Individuals: first request on setup, then remind at day 7, 14, and 21. Small companies: first request on setup, then remind at day 5, 10, 15, 20.” The numbers are days since the first request. The last number is the escalation point — if items are still missing by then, the file is handed to the preparer. A file with an earlier due date gets a tighter cadence; a file set up in early January with an April due date gets a looser one. The cadence reflects how much runway the client actually has.

Per-file overrides exist too. The checklist sheet has an optional cadence_override column. Type a comma-separated list of days and the tracker uses your numbers for that one client — the right escape hatch for the client who’s travelling for a month and asked you not to nudge until they’re back.

Gate 1: resolve the contact

The first place the dispatch looks is the file’s contact_email. If the client has added a handoff — a spouse on a joint return, or a business partner who actually handles the paperwork — that person is included too. If no contact is set at all (a half-finished file), the request isn’t sent into the void; the file is flagged for the preparer to fix. The fallback should never fire in steady state; if it does, the weekly digest names every file that hit it so the sheet can be corrected.

Gates 2 and 3: quiet hours and holidays

The tracker runs at 8am local, so the first send of a tick is already in business hours. But deferred sends and one-offs can land later. Gate 2 reads the quiet-hours setting (default 6pm to 8am) and, if the current local time is outside it, creates a one-off EventBridge Scheduler rule that fires at the next business minute and exits without sending. Gate 3 checks the date against the holiday list and, if it’s a configured holiday, defers to the next non-holiday business day. Both gates exist for the same reason: a request that lands at a sensible hour gets opened; one that lands at 11pm on a holiday gets buried.

Gate 4: compose with only what’s missing

The voice doc has one template per move — a first-request message and a reminder message. Gate 4 fills the template, but the crucial part is the list: it includes only the checklist items still open for this file, never the whole checklist. It attaches a fresh signed upload link (the old one may have expired) and the due date. For an escalate, the wording is different and the recipient is the preparer: it names how long the file has been waiting and which items are still missing, so the human has the full picture before calling the client.

Every send — request, reminder, or escalate flag — writes a row to td-sends in DynamoDB. The next day’s tick reads that row and knows not to send the same step again. That state is what makes the whole decision deterministic: a given file with a given cadence and a given send history always produces the same move, and re-running the tick sends nothing extra.

Why the daily tick uses no model

The tick could call a model to write a warmer reminder, or to decide whether to nudge at all. It doesn’t. The daily tick should be the one part of the system that’s utterly predictable — if the cadence says remind at day 14 and items are missing, the reminder fires. A model in that loop adds variance the practice can’t reason about, and it would burn a model call on the nine files out of ten that are healthy. Bedrock fires only on the upload lane in Part 3 and on the monthly summary in Part 6. The tracker itself is plain Python that reads a sheet and writes sends.

Next post: how a finished file reaches the preparer — the review, the three actions on a complete file, and how a rejected item turns into a fresh request for just that one document.

All posts