Part 4 of 7 · Cart recovery series ~5 min read

How a cart reminder reaches the shopper

The waiter picked a move — first reminder or second reminder. Now the sender has to figure out who to email, whether now is a decent time of day, whether this shopper has had enough already, and what the message should actually say. Get any of those wrong and the reminder is worse than none at all: a 3am ping, a second email to someone who unsubscribed, a generic blast that reads like spam. Four small guardrails sit between the move and the actual email.

Key takeaways

  • Address resolution: a cart with no email can’t be reminded — it’s skipped, not guessed.
  • Quiet hours defer the send to the next decent hour instead of pinging overnight.
  • A do-not-disturb check skips anyone who unsubscribed or got a reminder very recently.
  • Bedrock Haiku 4.5 polishes one line into your store’s voice, with a plain fallback if it’s down.
  • Every email carries the items, a return link, and a one-click unsubscribe; every send is logged.

Four guardrails on every send

Four guardrails between the waiter's chosen move and the sent reminder A horizontal flow diagram. On the far left, a "Move chosen" box: the waiter emitted an event with the cart and one of two sending moves — first reminder or second reminder. Four guardrail gates sit in a row to the right, each drawn as a vertical bar. Gate 1: Resolve address — reads the email on the cart row; if there is no email (the shopper never reached checkout), the cart is skipped rather than guessed at. Gate 2: Quiet hours — checks the local time against the rules-doc quiet-hours window (default 9pm to 8am); if outside sending hours, defers the send to the next decent minute via an EventBridge Scheduler one-off rule. Gate 3: Do not disturb — checks whether the email is on the unsubscribe list or got a reminder in the last few days; if so, the send is skipped. Gate 4: Final compose — fills the reminder template from the voice doc for the chosen move, asks Bedrock Haiku 4.5 to polish only the opening line into the store's voice (falling back to a plain line if Bedrock is unavailable), attaches the items, the cart total, a return link straight back to the cart, and a one-click unsubscribe. 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 wake-up knows the reminder has fired. Move chosen first or second reminder Gate 1 Resolve address email on cart? use it directly no email? skip — never guess an address Gate 2 Quiet hours local time in sending window? if not, defer via Scheduler Gate 3 Do not disturb unsubscribed or recent send? if yes, skip the send Gate 4 Compose + polish voice template for the move + items, total, return link, unsubscribe Send — SES outbound email to the resolved address SES SendRawEmail · List-Unsubscribe header for one-click opt-out every send logged to DDB cr-sends — the next wake-up won’t duplicate Every gate is a deterministic check — the only AI is one polished line, with a plain fallback.
Fig 4. Four guardrails between the move and the sent reminder. Resolve the address. Honor quiet hours. Respect do-not-disturb. Compose with the items, polish one line, then ship via SES and log the send so the next wake-up doesn’t duplicate.

Gate 1: resolve the address

The sender needs an email to send to, and the only honest source is the cart row itself. If the shopper entered their email at checkout before leaving, it’s on the row and the sender uses it. If they never reached the email field — an anonymous cart — there’s nothing to send to, and the cart is simply skipped. The system never buys, guesses, or matches an address from somewhere else. A reminder is only worth sending to someone who actually handed you their email; anything else is the kind of guessing that erodes trust fast.

Carts with an email but no name still get a warm, generic greeting (“Hi there”). The copy never pretends to know more about the shopper than the cart actually told you.

Gate 2: quiet hours

A cart abandoned at 11pm shouldn’t produce an email at 11pm, even if the wait technically elapsed. Gate 2 reads the rules doc’s quiet-hours setting (default 9pm to 8am, in the shopper’s timezone where known, otherwise the store’s). If the current local time is inside the quiet window, the sender creates a one-off EventBridge Scheduler rule that fires at the next decent minute and exits without sending. The Scheduler re-invokes the same sender with the same payload at the deferred time, where Gate 2 will let it through.

This is also why the waits in Part 3 are deliberately not razor-thin — a small buffer means the quiet-hours defer rarely has to push a send more than a few hours, so a cart abandoned late at night still gets its reminder first thing in the morning rather than days later.

Gate 3: do not disturb

Two things can make a send unwelcome even when the timing is fine. First, the shopper may have unsubscribed — from this cart, or from all reminders. Gate 3 checks the unsubscribe list and skips anyone on it, no exceptions. Second, the same person may have abandoned several carts in a short span; sending three reminders in two days, even for different carts, feels like a barrage. So Gate 3 also skips a send if that email got any reminder in the last few days (the window is configurable in the rules doc).

This gate is what turns “one reminder per cart” into “one gentle nudge per shopper.” The promise on the box — never pushy, one nudge not a barrage — lives mostly here.

Gate 4: compose, polish one line, then ship

The voice doc has one email template per move: a short, warm message with placeholders for the greeting, the items, the cart total, the return link, and the unsubscribe link. The sender fills the placeholders, then makes exactly one Bedrock Haiku 4.5 call — to rewrite just the opening line into your store’s voice, given the items and whether the cart was a saved-link cart. The model gets a tight prompt and a one-sentence budget; if it’s slow or unavailable, the sender falls back to the plain template line and ships anyway. The AI never touches the items, the total, the link, or the unsubscribe — only the tone of one sentence.

The composed email goes out via SES SendRawEmail, with a proper List-Unsubscribe header so the shopper’s mail client shows a one-click opt-out at the top, and a visible unsubscribe link in the footer that hits a Function URL. Both record the opt-out the same way. A second reminder uses the slightly warmer template and gently references that the cart’s still saved — never that “you ignored our last email.”

Every send — first or second — writes a row to cr-sends in DynamoDB. The next wake-up reads that row and knows not to send the same reminder again.

Why the guardrails exist

None of these gates are exotic. They’re the kind of small care a thoughtful shopkeeper would take if they were emailing each shopper by hand — only write to someone who gave you their address, don’t email at midnight, leave alone anyone who asked to be left alone or who you just wrote to, and say something warm and specific rather than a form blast. Putting them in code as four small sequential gates makes them part of the design, not something you’re trusting one email template to remember.

Next post: how a cart recovery stops the moment the shopper checks out — and the three ways a human can stop it by hand.

All posts