Part 4 of 7 · Inventory reorder bot series ~5 min read

How a draft PO reaches the owner

The bot picked a move — reorder or urgent reorder. Now the draft-PO Lambda has to figure out which supplier to order from, how many to order, when to send the draft, and what context to attach. Get any of those wrong and the draft is worse than no draft: an order for the wrong amount, sent to the wrong supplier, landing at 2am, with no way to see why. Four small guardrails sit between the move and the draft that lands in front of the owner.

Key takeaways

  • Supplier resolution: per-item override beats per-category default beats the configured fallback supplier.
  • Slack DMs are the default; email is the fallback if no Slack ID is configured for the owner.
  • Quiet hours defer a draft to the next business hour so nothing lands overnight.
  • The order quantity is rounded up to pack size, meets the minimum, and is capped by the rules doc.
  • Every draft ships with the item, suggested quantity, unit cost, supplier, and Approve/Edit/Skip.

Four guardrails on every draft

Four guardrails between the chosen move and the drafted PO A horizontal flow diagram. On the far left, a "Move chosen" box: the bot emitted an event with the item and one of two ordering moves — reorder or urgent reorder. Four guardrail gates sit in a row to the right, each drawn as a vertical bar. Gate 1: Resolve supplier — looks up the per-item supplier column first; if blank, falls back to the per-category supplier from the rules doc; if still blank, falls back to the configured fallback supplier. Returns the supplier's ordering email, pack size, and minimum order from the supplier doc. 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 draft to the next available business minute via an EventBridge Scheduler one-off rule. Gate 3: Order quantity — works out how many to order: enough to comfortably clear the reorder point, rounded up to the supplier's pack size, raised to the supplier's minimum order if needed, and capped at the rules-doc order cap so it never orders a wild amount. Gate 4: Compose draft — builds the purchase order from the supplier doc template, attaches the item context (name, SKU, suggested quantity, unit cost, line total, supplier, on-hand count, sales rate), and adds Approve, Edit, and Skip buttons. After all four gates pass, the draft is sent to the owner via the Slack API for the resolved Slack ID, or via SES outbound for the resolved email. A note at the bottom: every draft is logged to DynamoDB so the next check knows the item is already pending. Move chosen reorder or urgent reorder Gate 1 Resolve supplier item override? category default? fallback supplier? pull email, pack, minimum order Gate 2 Quiet hours local time in business window? if not, defer via Scheduler Gate 3 Order quantity clear the point, round to pack meet minimum, cap at the rules limit Gate 4 Compose + context PO template for supplier + item, qty, cost, total, 3 buttons Draft — Slack DM (preferred) or SES outbound email (fallback) Slack API for the DM · SES SendRawEmail for the inbox every draft logged to DDB ir-orders — the next check won’t duplicate Every gate is a deterministic check — no model calls, no surprise order on a quiet Tuesday.
Fig 4. Four guardrails between the move and the drafted PO. Resolve the supplier. Honor quiet hours. Work out a sensible quantity. Compose with full context. Then send to the owner via Slack or email and log the draft so the next check doesn’t duplicate.

Gate 1: resolve the supplier

Three places the draft-PO Lambda looks for the supplier of an item, in order. First, the stock sheet’s per-item supplier column — if a row names a specific supplier, that’s who the order goes to regardless of any default. Second, the per-category default in the rules doc (“all cleaning supplies default to Acme Wholesale”). Third, the configured fallback supplier — a catch-all so no item is ever stranded without somewhere to order from. The fallback should rarely fire; if it does, the weekly digest names every item that hit it so the supplier doc can be updated.

Once the draft knows the supplier, it pulls that supplier’s ordering email, pack size, and minimum order from the supplier doc — the numbers Gate 3 needs to size the order, and the address Approve will use later. It also looks up where to send the draft to the owner: the rules doc maps the owner to a Slack member ID if one is set, otherwise to an email address. Slack is preferred because a DM with action buttons is faster to act on than an email; email is the fallback so nothing falls through the cracks.

Gate 2: quiet hours

The bot runs early in the morning, so the first time a move fires it’s already near business hours. But an urgent draft that results from a sudden POS-driven dip can fire later in the day, and one-off deferred drafts can land outside the configured window. A purchase-order draft at 11pm helps nobody — it just sits unread until morning and risks an approval tap from a half-asleep owner.

Gate 2 reads the rules doc’s quiet-hours setting (default 6pm to 8am, configurable per business). If the current local time is in the quiet window, the draft creates a one-off EventBridge Scheduler rule that fires at the next business-hour minute and exits without sending. The Scheduler invokes the same draft-PO Lambda with the same payload at the deferred time, where Gate 2 will let it through. Urgent drafts can be configured to ignore quiet hours if a shop genuinely wants to be woken for a stockout risk — but the default is to wait.

Gate 3: a sensible order quantity

How many to order is its own small decision, and the bot makes it the same way every time. Start from how much you need to comfortably clear the reorder point and cover the next stretch of sales — a target stock level the rules doc can set as a number of days of cover (say, 30 days). Subtract what’s on hand. That’s the raw quantity. Then three adjustments, all from the supplier doc: round up to the supplier’s pack size (you can’t order half a case), raise to the supplier’s minimum order if the raw number is below it, and finally cap the result at the rules-doc order cap so a typo in the sales rate can never produce a five-figure order.

The cap is the important guardrail. Every other gate makes the draft more right; the cap is the one that makes a wrong draft small. If a sales rate gets fat-fingered to 800 instead of 8, the cap means the worst the bot can propose is the capped quantity — a number the owner will immediately see is off, rather than a catastrophic auto-order. And because nothing sends without the owner’s tap anyway, even a capped-but-wrong draft is caught before any money moves.

Gate 4: compose with full context, then send

The supplier doc has one purchase-order template per supplier: a short order with placeholders for the item name, SKU, quantity, unit cost, line total, and any standing instructions (delivery address, account number, PO prefix). The draft-PO Lambda fills the placeholders, attaches the on-hand count and sales rate so the owner has the “why” at a glance, adds Approve, Edit, and Skip buttons, and sends the message to the owner via the Slack API. The supplier’s ordering email isn’t touched yet — that only happens on Approve, in Part 5.

For email fallback, the same draft is wrapped in a small HTML email with the same fields and three links that, when clicked, hit a Function URL that records the choice — the email equivalent of the Slack buttons.

An urgent draft is composed slightly differently: it carries an “Urgent” tag, sorts to the top of the owner’s queue, and includes the days-of-cover-left figure (“~2 days at current sales”) so the owner knows exactly how much runway is left. If the rules doc names a faster backup supplier for the item, the urgent draft offers it as a one-tap alternative in the Edit modal.

Every draft — Slack or email, reorder or urgent — writes a pending row to ir-orders in DynamoDB. The next day’s check reads that row and knows not to draft the same item again while it’s awaiting a decision.

Why the guardrails exist

None of these gates are exotic. They’re the kind of small care a thoughtful buyer would take if they were placing the order themselves — check who we actually buy this from, don’t draft at 11pm, order a round case rather than an odd number, and never let a typo turn into a giant order. Putting them in code as four small sequential gates makes them part of the design, not a feature you’re trusting the person on shift to remember.

Next post: how a reorder gets approved once the owner has seen the draft — how Approve sends the PO and marks the item on-order, how Edit changes the order first, and how Skip steps back without losing the thread.

All posts