Part 1 of 7 · Waitlist manager series ~5 min read

A waitlist manager on AWS for a few dollars a month

A salon, a clinic, and a restaurant all share one quiet leak: a slot that nobody fills. A client cancels the 2pm cut at 1:40. A patient doesn’t show for the 10am. A four-top calls off an hour before. The chair sits empty, the room sits empty, the table sits empty — and there were people who would have jumped at that time, if only someone had phoned them. This post walks through the design of a small system that keeps a waitlist, notices the moment a slot frees up, offers it to the next suitable person with a short claim window, and confirms the booking — without anyone on staff working the phones.

Key takeaways

  • Three sources for waitlist entries: a Drive list, an inbox forwarding lane, and a web form.
  • Every freed slot ends in one of four moves: nobody fits, make an offer, roll on, or hand back to staff.
  • Fair order by join time, but only among entries that actually fit the slot. A priority flag can move someone up.
  • One offer is live at a time and a single conditional write books the slot — so two people never get the same one.
  • Designed on AWS for about $2/month at typical small-business volume.

The whole system on one page

Before any code, here’s the shape of what we’re designing.

System architecture: three sources, three pieces inside AWS At the top, three external boxes in a row. Far left, "Waitlist" — a Google Drive list with one row per person waiting, plus an inbox forwarding lane and a web form that add new entries. Centre, "Rules and voice" — a Drive folder with a rules doc covering the fair order, the eligibility checks, the claim-window length, and quiet hours, plus a voice doc with the offer message templates. Far right, "Waiting people" — the customers on the list; offers land in their text messages or email. Each connects via an arrow to the AWS account container below. The waitlist has an outgoing arrow into AWS. Rules and voice feed in to ground every offer. Waiting people receive an offer with the freed slot, the time and date, a one-tap claim link, and a countdown. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Entry intake — receives entries from each source, parses inbound booking requests, and writes a clean row into the waitlist. In the middle, the Offer engine — wakes when a slot frees; finds who fits; sorts in fair order; picks one of four moves: nobody fits, make an offer, roll on, or hand back to staff. On the right, the Sender — sends the offer via text or email, respects quiet hours, and confirms the booking when someone claims. Internal arrows flow left to right. A note at the bottom reads: only one offer is live per slot — a single conditional write means two people never get the same one. Waitlist list, inbox, web form Rules and voice order, window, templates Waiting people where offers land entries in grounds offer + claim link AWS account Entry intake parse, normalize, add to waitlist Offer engine picks one of four: no fit, offer, roll, hand back Sender text or email, respects quiet hours entry offer Only one offer is live per slot — a single conditional write means two people never share one.
Fig 1. Three sources outside, three pieces inside AWS. Entries flow in from a Drive list, an inbox forwarding lane, and a web form. The Offer engine wakes when a slot frees and picks one of four moves. The Sender gets the offer to the right person with a short claim window.

What you set up once (the outside)

  • The waitlist. A Google Sheet in a Drive folder, one row per person waiting: name, contact (mobile and/or email), the service or table they want, party size, the earliest and latest dates that work for them, any staff preference, a priority flag, and the time they joined. You can fill it in by hand, but most entries arrive through the two other lanes covered in Part 2 — an inbox-forwarding lane (forward a booking request and the system proposes a row for one-tap approval) and a web form (a “join the waitlist” page that drops a row straight in).
  • A rules folder. Two short Google Docs in a Drive folder. The rules doc holds the order and the checks. The order is first-come, first-served by join time unless a priority flag moves someone up. The checks decide who actually fits a freed slot — right service, right party size, the date inside their window, a matching staff preference if they set one. The doc also holds the claim-window length (default 10 minutes), the quiet hours, and how many people to try before handing the slot back to staff. The voice doc holds one offer message template per channel — what the text or email actually says.
  • Waiting people. The customers on the list. Each one has a mobile number (so the offer is a text) or, if no number is set, an email address. Offers land with the freed slot’s date and time, the service, a one-tap claim link, and a countdown that says how long they have to take it before it rolls to the next person.

What runs when a slot frees (the inside)

  • The entry intake. Three sources feed the waitlist. The Drive sheet itself is the canonical store. New entries can also arrive via the inbox forwarding lane (forward a booking request to waitlist@your-business.com; the system uses Textract to read any attachment and Bedrock Haiku 4.5 to pull out name, service, party size, and dates, then drops a one-tap approval card for a staffer to confirm before the row is added) and the web form (a simple hosted page that writes a clean row directly).
  • The offer engine. Wakes the moment a slot frees — a cancellation, a no-show marked by staff, or a fresh opening. It reads the waitlist, keeps only the entries that fit the slot, sorts them in the fair order from the rules doc, and picks one of four moves. Nobody fits: the list has no eligible entry — leave the slot open and tell staff. Make an offer: send the slot to the top eligible person and start a short claim window. Roll on: the window ended unclaimed — send the same slot to the next eligible person with a fresh window. Hand back to staff: the list is exhausted or the cut-off is reached — return the slot with a note. The engine calls no model on this path — the move logic is plain Python.
  • The sender. Reads the voice doc, formats the offer for the chosen channel, and sends it. Texts go through SNS; email goes through SES outbound. Both honor quiet hours so a 6am cancellation doesn’t wake the whole list. The claim link points at a Lambda Function URL; the first valid claim flips the slot to booked with a single conditional write, sends a confirmation, and cancels the window timer. Every offer, claim, and roll writes a row in DynamoDB so the trail is clean. A monthly summary writes an owner-ready paragraph: slots freed, slots filled, average time-to-fill, and how much empty-chair revenue was recovered.

In plain words

It’s a Saturday salon. The 2pm colour cancels at 1:35. Three people are on the waitlist for a colour this week. The engine keeps the two whose date window includes today, drops the one who asked for a specific stylist who isn’t in, and sorts the remaining two by who joined first. It texts the first: “A colour opened up today at 2:00pm with Jess. Tap to claim — you have 10 minutes before we offer it to the next person.” She’s at lunch and doesn’t look. At 1:45 the window ends; the offer rolls to the second person, who claims it in two minutes. The slot flips to booked, she gets “You’re confirmed for 2:00pm today”, the first person’s link goes dead, and the front desk sees the chair is filled. Nobody phoned anyone.

The cost of running this is about $2 a month at SMB volume. The cost of not running it is every empty chair, room, or table that someone on a list would have happily taken — revenue that walks out the door because nobody had time to work the phones in the ten minutes that mattered.

Design rules that shaped every decision

  • Only one offer is live per slot. The next person is never texted until the current window ends or the slot is declined.
  • Four moves, always. Nobody fits, make an offer, roll on, hand back to staff. There is no fifth.
  • Fair order is written down. Join time by default; a priority flag can lift someone; the same inputs always give the same order.
  • Eligibility is checked before anyone is offered — right service, party size, date window, staff preference. No offer that can’t actually be honored.
  • The claim is a single conditional write. The first valid click wins; every later click is told the slot is taken. No double-booking.
  • Every offer, claim, roll, and time-out is logged. Audit a busy Saturday later and you can see exactly what happened.

Why this shape

Most businesses “run a waitlist” on a sticky note by the till or a note in someone’s head. It works right up until the slot actually opens — at which point the person who could fill it is busy with a customer, the list is in a drawer, and by the time anyone gets to the phone the moment has passed. The few tools that automate this tend to blast the whole list at once, which creates the opposite problem: ten people race for one slot, nine get a “sorry, taken”, and you’ve trained your best customers to ignore your texts.

The setup above keeps the list somewhere staff already edit, but adds a small system that watches for a freed slot and acts the instant one appears. It offers to one person at a time, in an order that’s fair and written down. It gives a real countdown so nobody sits on an offer forever. It rolls on cleanly when somebody’s not looking. And the booking is locked by a single write, so two people are never sent to the same chair. The engine is invisible most of the day; it only does something in the few minutes after a slot opens — which is exactly when it matters.

The next four posts walk through each piece in turn: how a waitlist entry gets added, how a freed slot gets noticed, how an offer reaches the next person, and how a slot gets claimed or rolls on. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts