How an offer reaches the next person
The engine picked a move — make an offer, or roll on to the next person. Now the sender has to figure out who exactly to contact, on what channel, at what time of day, and with what link attached. Get any of those wrong and the offer is worse than useless: a 6am text, a dead link, an offer to a number that’s been disconnected. Four small guardrails sit between the move and the offer actually landing.
Key takeaways
- Pick the next eligible entry in fair order — only one offer is live per slot at a time.
- Texts are the default; email is the fallback if no mobile number is on the entry.
- Quiet hours hold an offer until business hours — a dawn cancellation doesn’t wake the list.
- Every offer ships with the slot, the time, a one-tap claim link, and a live countdown.
- A claim-window timer is armed on send; when it ends, the offer rolls to the next person.
Four guardrails on every offer
Gate 1: pick the next entry, resolve the channel
Part 3 already filtered and sorted the list, so Gate 1’s job is to take the next person who hasn’t been tried yet for this slot — the top of the eligible, fair-ordered list minus anyone already offered and timed-out. Before it does anything, it double-checks that no other offer is currently marked live for this slot in wl-offers. That check is what keeps the “one offer at a time” promise: even if two events arrive close together (a webhook and a timer firing at almost the same moment), only one offer goes out.
Once the sender knows which entry to contact, it looks up the channel. The entry’s contact field gives a mobile number if one was provided — texts are preferred because a slot opening today needs a reply in minutes, and a text is read in minutes. If there’s no mobile, it falls back to the email address. Either way, the claim link works the same.
Gate 2: quiet hours
Slots free up at all hours — a customer cancels their morning appointment at 6:15am, a no-show is marked at 8:50pm. The offer itself is fine to compute then, but sending it then is not: an offer that lands while someone’s asleep wastes the claim window on a phone that won’t be looked at, and a 6am text is a fast way to get a customer to mute you.
Gate 2 reads the rules doc’s quiet-hours setting (for example, no offers between 9pm and 8am, configurable per business). If the current local time is inside the quiet window, the sender holds the offer: it creates a one-off EventBridge Scheduler rule that fires at the next business-hour minute and exits without sending. At that time the same send Lambda runs with the same payload, Gate 2 lets it through, and only then is the claim window armed — so the countdown always starts when the offer is actually delivered, never while it’s being held.
Gate 3: arm the claim window and the timer
This is the gate unique to a waitlist. Before the offer goes out, the sender writes the live offer to wl-offers with a window_ends_at timestamp (now plus the claim-window length from the rules doc, default 10 minutes). It mints a short-lived claim token tied to this exact offer — (slot_id, offer_seq) — so the link can only book this one offer and goes dead the instant the offer rolls on. And it schedules an EventBridge Scheduler one-off rule for window_ends_at that, if the slot is still open at that time, fires a roll-on event back to the engine.
Arming the window before sending matters: if the send fails, there’s no orphaned live offer with no message behind it, because the write and the schedule are part of the same handler and a send failure rolls the offer back to pending for a clean retry.
Gate 4: compose with the claim link, then ship
The voice doc has one offer template per channel: a short message with placeholders for the service, the date and time of the freed slot, the staff member, the claim link, and the countdown (“you have 10 minutes”). The sender fills the placeholders, attaches the tokenized claim link, and ships — via SNS for a text, or wrapped in a small HTML email via SES for the email fallback. The link points at the claim-handler Function URL covered in Part 5; tapping it is what books the slot.
A roll-on offer uses the same template — the next person has no idea they’re second in line, and shouldn’t; from their side it’s simply “a slot opened, here it is, you have ten minutes.” Every offer, first or rolled, writes its row to wl-offers. The timer, the claim, and the next roll all read that table, so they always agree on which offer is live and which have already passed.
Why the guardrails exist
None of these gates are exotic. They’re the care a thoughtful person at the front desk would take if they were working the list by phone — call the right person next, don’t ring at 6am, give them a real but finite chance to say yes, and make sure the offer you give can actually be honored. Putting them in code as four small sequential gates makes them part of the design, not something you’re trusting a busy staffer to remember on a Saturday.
Next post: what happens when the offer lands — how a single conditional write books the slot for exactly one person, and how an unclaimed window rolls cleanly to the next.
All posts