On this page
The case that started this was small. A lead was offered 9:15, said yes, and was told the time had gone: another lead had said yes to the same 9:15 four minutes earlier. Nobody had done anything wrong. The assistant had offered both of them the earliest free time, and only one of them could have it.
It happened whenever leads arrived close together. An SMS campaign to an old lead list starts conversations with up to 10 people a minute (campaigns are on the Business plan), so the replies come back in a bunch. Inquiries also arrive in bursts after an ad goes live, or on a busy Monday morning. Every conversation that reached the booking step got the same briefing: the earliest free times in the workspace, where only booked meetings counted as taken. So they were all offered 9:00. The first “yes” booked it, everyone else heard “sorry, that’s taken, how about another time?”, and that extra round trip is where leads drop off. It’s double booking that no calendar shows, because a calendar only sees the one meeting that got booked.
So the Send4 assistant now holds the time it offers, for two hours by default, and the next lead is offered the next free time. Play the burst below with holds off, then on.
- AmyBooked Mon 09:00
- BenBooked Mon 10:00
- ChloeBooked Mon 11:00
- DanielBooked Mon 12:00
- EllaBooked Mon 13:00
- FinnBooked Mon 14:00
- GraceBooked Mon 15:00
- HarryBooked Mon 16:00
- IslaBooked Tue 09:00
- JackBooked Tue 10:00
With holds on, 10 leads got 10 different times, and nobody had to be asked again.
Why the obvious fixes don’t work
Three fixes come to mind first, and none of them works. Offering two times instead of one doesn’t help, because the second time isn’t held either and collides the same way, so the assistant went the other way: it offers one time, as a yes or no question. Letting the AI remember what it offered doesn’t help either. The offered time only existed as text in a message, so nothing could reserve it. And booking the time straight away fills the calendar with meetings nobody agreed to.
A soft hold, not a booking
In calendar terms this is a tentative hold: the time is kept back while the lead decides, and nothing is booked yet. In Send4 a hold is its own record rather than a kind of meeting, because everything that reads meetings, from the calendar to exports and webhooks, would otherwise have to learn to skip it.
A hold lasts two hours by default. You set the length on each booking step, from 0, which turns holds off, up to 24 hours. It ends early if the minimum-notice deadline comes first: with one hour’s notice, a hold placed at 08:55 on 10:00 expires at 09:00, because nobody could book 10:00 after that anyway. More messages don’t extend it, so a chatty lead can’t keep a time forever. Holds apply across the whole workspace, not per salesperson.
The hold is soft because we don’t want to turn away a lead who is ready to book for the sake of one who might be. While an unheld time is available, the assistant skips times held for other leads. But a lead who asks for that exact time can still book it, and the first to confirm wins. The next time the other lead writes, the assistant tells them the time went and offers another one. If every free time is held, the assistant offers the earliest one anyway, without creating another hold. Offering a contested time beats offering nothing.
How to avoid double booking: the system design
The rule is easy to say: don’t offer a time someone else is deciding on. The first decision was to keep the AI model out of choosing times. The picker chooses, and the assistant is told to offer exactly that time. If the lead wants a different one, the assistant has to call a tool that moves the hold, so every time it proposes is a record, not just words in a message, and the booking tool has the final say. The rest is for developers, and for anyone designing an appointment booking system. We wrote the plan on 25 September and built it two days later, and two reviews of the plan added most of the edge cases below.
Buffer time between appointments
With 30-minute meetings and a 10-minute buffer, the second lead gets 10:00, not 09:30. If both said yes, 09:30 would fall inside the first meeting’s buffer, and the booking would be refused. So the gap from the first meeting’s end to the next meeting’s start must be at least as long as the larger of the two buffers:
gap between meetings ≥ max(buffer A, buffer B)
It’s the larger of the two because two booking steps can have different buffers. Drag the lengths and buffers below and watch where the next meeting can start.
gap between meetings ≥ max(buffer A, buffer B)
30 min ≥ max(10, 10) = 10 min
The next lead is offered 10:00. From the end of the first lead’s meeting to the start of the next, the gap is 30 minutes, and the larger of the two buffers is 10. A meeting at 09:30 would leave a gap of 0 minutes, less than 10, so it would be refused.
Length matters the same way. A booking step can offer services of different lengths, and until the lead chooses one, we reserve enough calendar time for the longest, so a 30-minute and a 90-minute service push the next lead to 11:00. Once they choose, we adjust that interval to the chosen service’s duration. The picker runs the clash check against meetings and against other leads’ holds, and holds the first time that passes:
# Pick one time to offer a lead, and hold it.
function offerTime(lead):
for time in openTimes(after: now + minimumNotice):
if clashes(time, meetings): continue
if clashes(time, holdsOfOtherLeads): continue
hold(time, lead, until: earliest(now + holdLength, time − minimumNotice))
return time
# Every open time is held: offer the earliest anyway, unheld.
return firstOpen(openTimes, ignoring: holds)
# Two meetings clash when the gap from one’s end to the other’s start is too small.
function clashes(a, b):
return gap(a, b) < max(a.buffer, b.buffer)
When a hold stops counting
A hold counts only while its conversation is on the booking step. A conversation that ends, or moves on to the next step, stops blocking the time, so no exit path has to remember to clean up after itself. While someone on your team has taken over a conversation, its held time can be offered to other leads too, and a time the lead turns down goes back on offer at once.
Before the assistant offers a held time again, it checks it against the same rules as a booking. Opening hours can change, a blackout day can be added, or someone can book a meeting by hand. A time that no longer fits is let go, and the lead is offered another. A late yes works the other way. If a lead says yes three hours later and the time is still free, they get that same time. The assistant doesn’t switch to a new one, because the lead said yes to a time, not to whatever is free now.
Rehearsals stay out of all of this. A test conversation in the flow builder never takes a real lead’s time, and a test booking never takes a real lead’s held time.
The race condition in our booking system
We found this one on the way, and it’s the textbook double booking problem: two bookings for the same empty slot at the same moment could both succeed. The check before a booking locked the meetings already in that slot, and an empty slot has none, so there was nothing to lock. Both bookings checked, both saw nothing, and both saved a meeting. Our AI queue ran two workers, so two yeses really could be handled at the same moment, and the meetings table had no constraint to catch it.
We closed it while building holds. Picking a time and booking one now take the same lock, one per account, so the second booking waits until the first is done and finds its meeting. The assistant then offers the second lead another time. If the lock can’t be had, the booking goes through as it did before, because a customer’s yes must never fail because of our cache. Step through it below, without the lock and with it.
- 09:00 Amy
Step 7 of 7
With the lock, Ben’s booking waited, found Amy’s meeting, and was refused. The assistant offered Ben 10:00, the next free time.
What you see in Send4
Held times show on the Send4 calendar as hatched “Offered to …” blocks, next to the calls already booked. They aren’t meetings and aren’t counted as meetings, and selecting one opens its conversation.
You set how long a time is held on each booking step. The help centre has the steps: how offered times are held.
What a hold doesn’t do
A hold is soft, so two leads can still collide when one of them asks for the time the other is holding. It isn’t a guarantee. It also knows only the Send4 calendar. Does the assistant check the salesperson’s own calendar before it offers a time?
Not yet. Bookings go into the Send4 calendar.
There’s no availability per salesperson either: every booking step offers the times that are free in the one Send4 calendar. And a big burst can fill a morning with holds, so later leads are offered the afternoon or the next day. If that happens, shorten the hold on that booking step.
If you book calls with leads, this is what SMS appointment booking in Send4 does: the assistant asks your questions, holds a time, and books the call in the same conversation.
Questions
What is double booking in scheduling?
Two meetings in the same slot, or two people each told that the same time is theirs. When an AI assistant answers many leads at once, the second kind is the common one, because everyone is offered the earliest free time.
How do you avoid double booking appointments?
Hold a time while you offer it, keep a buffer between meetings, and check the slot again when the person says yes. The Send4 AI assistant holds each time it offers for two hours by default, so the next lead is offered the next free time.
What is a tentative hold?
A time kept back for someone while they decide, without booking it. Send4 calls it a soft hold. While an unheld time is available, the assistant skips times held for other leads, and if every free time is held, it offers the earliest one without creating another hold. A lead who asks for a held time by name can still book it, and the first to confirm gets it.
What is the purpose of buffer time between appointments?
It leaves room after one meeting before the next can start. In Send4 it’s 10 minutes by default. Between two held times, the gap from the end of the first meeting to the start of the next is at least the larger of their two buffers.
Can AI book appointments?
Yes. The Send4 AI assistant books calls by text, or by email on the Business plan, into the Send4 calendar, and holds each time it offers while the lead decides.
How is an AI assistant different from an appointment booking system?
With an appointment booking system, the customer picks a free time on a calendar. The Send4 AI assistant answers the inquiry by text, or by email on the Business plan, asks your questions and offers one time. That time is held while the lead decides, and the booked call goes into the Send4 calendar.
Does Send4 work with Google Meet or Microsoft Teams?
Not yet. The call is booked in Send4, and you send the link.
Does it check my own calendar before it offers a time?
Not yet. Bookings go into the Send4 calendar.
Are held times put on my calendar?
No. A hold lives in Send4 only. It shows on the Send4 calendar as a hatched block, and it isn’t a meeting.
Viktoras Lava
CTO
Viktoras builds the Send4 product. He shipped slot holds in September 2026.