Part 3 of 7 · Onboarding guide series ~5 min read

How an onboarding step comes due

Once a day, at 9am local time, an EventBridge Scheduler rule fires the guide Lambda. The Lambda reads the onboarding list, looks at one customer at a time, computes how many days it’s been since they signed up, checks which steps they’ve already finished, and decides whether to do nothing or to send something — and if so, which kind. The whole decision is plain Python. No model. No vector retrieval. Every step and every window lives in the rules doc, where a rep can edit it without a deploy.

Key takeaways

  • The guide runs once a day via EventBridge Scheduler at 9am local time.
  • Per-plan step plans live in the rules doc — starter gets welcome at day 0, setup at day 2, check-in at day 6.
  • Four moves per customer, every tick: on track, next step due, gentle nudge, flag to owner.
  • DynamoDB tracks which steps were sent and which are done so messages aren’t duplicate spam.
  • The guide itself never calls a model. The decision is entirely deterministic.

The decision flow, per customer

Decision flow per customer on every daily tick A vertical decision flow diagram. At the top, an input box "Customer from list" with the row's name, plan, signup date, current step, and DynamoDB state. Below that, a step "Compute days_since_signup" — today's date in the configured timezone minus the signup date. Below that, a check "Paused or finished?" — if yes, route to "On track" (do nothing this tick). If no, continue. The next step "Look up step plan for plan" — pulls the plan from the rules doc; for example a starter plan returns welcome at day 0, setup at day 2, check-in at day 6. The next step "Find the step that is due now" — picks the next step whose day has arrived and which the customer has not finished. If none is due, route to "On track." If one is due, look at the DynamoDB state to see whether that step's message has already been sent. If it has not been sent, route to "Next step due" (send the step message). If it has been sent but the step is still not done and its window has passed, route to "Gentle nudge." If the final window for that step has passed and it is still not done, route to "Flag to owner" — tell the owner named in the rules doc so a human can reach out. Each terminal box — On track, Next step due, Gentle nudge, Flag to owner — emits an event to EventBridge with the move and the customer context. A note at the bottom: the rules doc holds every step and window; the guide's code only enforces them — change a step in the doc and tomorrow's tick uses the new value. Customer from list name · plan · signup · step Step 1 Compute days_since_signup today (TZ) − signup_date Step 2 Paused or finished? read DDB og-state table Step 3 Look up step plan e.g. starter → [0, 2, 6] Step 4 Which step is due now? none → on track final window → flag Step 5 Already sent this step? read DDB og-sends table On track do nothing Next step due send the step Gentle nudge step still undone Flag to owner final window, stuck if yes none final unsent sent, stalled The rules doc holds every step — change a window and tomorrow’s tick uses the new value.
Fig 3. The guide’s decision tree, per customer, per daily tick. Five steps decide which of four moves applies. The rules doc holds every step and window; the guide only enforces them.

Step plans: day 0/2/6 isn’t magic, it’s in the doc

The rules doc has one short section per plan. Each section names the steps in plain prose: “Starter plan: welcome on day 0, setup on day 2, check-in on day 6. Pro plan: welcome on day 0, connect data on day 1, feature tour on day 3, invite team on day 5, check-in on day 9.” The numbers are days after signup when each step’s message should land. Each step also has a window — how many extra days to wait, with no progress, before sending one gentle nudge, and then how many more before flagging the owner.

The plans exist for a reason. A starter customer wants to be useful fast and out of your inbox; three light steps over a week is plenty. A pro customer has more to set up, so the plan spaces things out and earns the right to send more. The shape of the plan reflects what the customer actually needs to do, not a fixed marketing drip.

Per-customer overrides exist too. The onboarding sheet has an optional column called plan_override. Name a different plan there and the guide uses that plan’s steps for that one customer. This is the right escape hatch for the enterprise account that needs the long plan even though they bought the starter tier.

Four moves, always

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

  • On track. Nothing is due, the customer is moving fast and finishing steps ahead of schedule, or they’re paused or finished. Do nothing. Most customers, most days, are on track.
  • Next step due. A step’s day has arrived and the customer hasn’t done it yet, and the message hasn’t been sent. Send the step message with full context. Write a row to the og-sends DynamoDB table marking that this step has been sent.
  • Gentle nudge. A step was sent, its window passed, and the customer still hasn’t finished it. Send one friendly follow-up that names the original message so the customer doesn’t feel like they’re seeing it for the first time. Write the nudge to og-sends. There is exactly one nudge per step — never a chain of them.
  • Flag to owner. The final window for a step passed and it’s still undone. Don’t message the customer again. Instead, tell the owner named in the rules doc — usually whoever handles onboarding — so a human can reach out. Mark the customer as flagged in DynamoDB; the guide won’t flag the same step twice. Spamming a stuck customer with more automated mail is exactly the wrong move; a real person is the right one.

State that makes the decision deterministic

The guide reads two DynamoDB tables every tick. og-sends records every message that’s gone out: (customer_id, step_id, kind, sent_date) where kind is step or nudge. og-state records each customer’s progress: (customer_id, steps_done, paused, finished, flagged). With those two tables, the move-decision logic is a few dozen lines of Python and zero magic. A given customer with a given signup date, a given plan, and a given send/done history always produces the same move. Re-running the tick produces no extra messages (because the state in DDB shows what already fired).

When a customer marks a step done — via the one-click link in Part 4 — their row in og-state gets the step added to steps_done. The next tick sees it’s done and moves on to the next step, or marks them finished if it was the last one.

Why the daily tick uses no model

The guide could call a model on the tick to write a smarter message, or to guess whether a customer is the type who needs more help. It doesn’t. Two reasons. First, the daily tick should be the one part of the system that is utterly predictable — if the rules doc says the setup step lands on day 2 and it isn’t done, the message goes out. A model in that loop introduces variance the team can’t reason about. Second, model calls cost money, and most days most customers are on track, so the call would be wasted nine times out of ten.

Bedrock fires elsewhere — on the occasional personalized rewrite of a step message and on the monthly summary mentioned in Part 6. Not on the daily tick. The guide itself is plain Python that reads a doc and writes events.

Next post: how a message finds the right customer, how quiet hours and weekends are honored, and what the one-click done link actually does.

All posts