Bangkok Paper Ledgers and Manual LINE Bookings: Seating Practice That Stops Double-Booking and Protects 150,000 THB a Month

Bangkok Paper Ledgers and Manual LINE Bookings: Seating Practice That Stops Double-Booking and Protects 150,000 THB a Month
BLUF: Double-booking is a concurrent inventory write, not a staff mistake
Friday 20:00 private rooms in Thong Lo and Phrom Phong are not double-sold because hosts lack attention. They are double-sold because a paper diary that is still being copied, and a LINE chat that treats ได้ค่ะ as a confirmed seat, both write the same room ID with no mutex. The 15,000 THB collision is not the loss. Ghost holds that block a table while the guest still shops, notes that stay in a pocket, and over-defensive walk-in refusals when the notebook is unreadable, together strip about 150,000 THB a month from a 48-seat dinner house. Speed of chat reply does not fix this. One ledger of table-time inventory, with a pessimistic write lock that lands on every iPad and on the LINE intake at the instant of confirm, does. Adding a booking engine while keeping paper and personal chat as extra mouths widens the collision window.
1. Count the loss as a dead 20:00 slot, not as a collision count
A 40- to 50-seat Japanese or yakiniku room on Thong Lo Soi 13 or Phrom Phong Soi 39 concentrates demand on private rooms and four-plus combined tables at Friday and Saturday 20:00. Japanese corporate entertainment wants a quiet room and a ranked seat. Dwell is 135 to 150 minutes. The same night, a Thai six- to eight-top hits the Official Account. A one-page-per-day paper diary cannot write both demands at once.
Counting “how many collisions this month” sends the wrong decision. Four layers matter.
- Hard collision. The same private room and the same start time are both marked confirmed. Six covers at 2,200 THB is 13,200 THB on the check, plus apology drinks and a next-visit discount.
- Ghost hold. A LINE message asks “tonight 20:00, four people?” The host replies ได้ค่ะ without looking at the book. The guest keeps comparing restaurants. The shop has closed a 90-minute slot. If they never arrive, the table sits empty. If they do, the collision seed is already planted.
- Untranscribed vacancy. A phone booking taken during prep is scribbled on the back of a docket and dropped into an apron. The 20:00 four-top is physically empty and does not exist on the book of record.
- Over-defensive refusal. The terrace host cannot read the notebook (smudge, overlapping handwriting, a page still at the pass) and turns a walk-in away as full.
Place the arithmetic on a 48-seat room, 2,200 THB dinner check, eight peak nights a month centered on Friday and Saturday.
- Hard collisions, 2 per month: 6 × 2,200 × 2 = 26,400 THB. Compensation 2,500 × 2 = 5,000 THB.
- Ghost holds, 10 slots, 50% would have converted if resold: 4 × 2,200 × 10 × 0.5 = 44,000 THB.
- Untranscribed vacancy, 6 slots: 3 × 2,200 × 6 = 39,600 THB.
- Over-defensive refusal, 4 slots: 3 × 2,200 × 4 = 26,400 THB.
Subtotal about 141,400 THB. Add confirmation-call and chat-transcription overtime (25 hours × 90 THB = 2,250 THB) and you sit near 144,000 THB. A single corporate account that spends about 300,000 THB a year walking out after seeing its room occupied is the tail. 150,000 THB a month is not the sum of collisions. It is the yield you lose when people cannot exclusively control 20:00 inventory.
A six-top of directors who turn around at the door does not end at 13,200 THB that night. The next banquet and the referral stop. “Be more careful” leaves the Friday reproduction conditions intact.
2. Floor notes, Reddit, and X describe the same collision
Shop failure and guest posts point at one structure.
On the floor. A Japanese GM takes a corporate call at 16:30 and writes “20:00 Sakura 6 Tanaka” on the back of a docket. At 17:10 a Thai host sees the Official Account: “20:00 8 people private room?” The notebook is at the pass. The docket is in the GM’s apron. The host answers ได้ค่ะ รอได้เลยค่ะ. At 17:45 the GM copies the note into the diary. Both parties arrive just before 20:00.
On the diner side. r/ThailandTourism and r/Bangkok regularly carry “I messaged the shop LINE, it stayed read for over an hour, I booked elsewhere” and “we had a reservation, we arrived, the table was already sat.” Japanese-traveler threads compare shops that confirm on a web book or TableCheck against shops that only answer LINE, then drop the slow ones. TableCheck’s own diner help telling guests that an unattended duplicate may be treated as a no-show is evidence that delayed confirms and double-sends exist in production, not only in vendor decks.
On the operator side (X). Thai-language posts complain about staff who reply ครับ and never write deposit, party size, or whether the private room is actually available. Japanese GM accounts inspect themselves: “only I can read the paper book, and on my day off the floor gives the room away on LINE.” After TableCheck and Hungry Hub announced inventory sync in June 2024, travelers posted that they could finally hold a Thai table in Japanese. That is not the remaining shop problem. If OTA and engine inventory sync, paper, personal LINE, and Instagram DMs remain third and fourth write mouths, Friday 20:00 rooms still sell twice.
The guest implication is simple. People want to book. They refuse (a) no proof of confirm, (b) arriving to no table, (c) read-and-ignored chat, and (d) ได้ค่ะ meaning either “we received your message” or “the seat is yours,” with no way to tell which.
3. Feature comparison: four ways to “take a reservation”
“Digitize bookings” is a slogan. Implementation decides Friday collision rate and monthly leakage.
| Dimension | Paper diary + manual LINE | Spreadsheet / Google Form | Foreign engine + OTA sync | Rezabo exclusive-lock ledger |
|---|---|---|---|---|
| Book of record | Notebook at the pass. Pre-copy notes do not exist | Sheet or form inbox. Last save wins | Engine grid. Sync covers only contracted channels | Cloud table × time. LINE intake is the same event |
| Definition of confirm | Chat ได้ค่ะ or spoken yes | Form submitted. Host later drops it onto a table | Screen complete. Paper and OA chat stay off-sync | Confirm tap after lock. Empty slots only |
| Concurrent write | None. Phone, LINE, walk-in run in parallel | Last write wins. Collision found later | Strong inside the engine. Third channel is a separate contract | 15-second pessimistic lease. Other terminals gray out |
| Time per intake | Check + copy: 4 to 9 minutes | 3 minutes of typing plus hours of eyes-on | Guest path is short. Staff still remap OTA rules | Phone ~15 seconds. LINE completes on slot select |
| Language | Japanese scrawl misread by Thai hosts | English column names send the floor back to paper | Multilingual on the brochure; paper still on the floor | Thai, English, Japanese on one grid |
| Sticker price | Software 0. Leakage and labor are the cost | Form 0. Build plus eyes-on labor | Unpublished quote. Hungry Hub prepaid packages are extra | Ledger 1,900 THB/month, unlimited reservations. LINE quota is the OA plan |
| Offline | Paper works. The book of record splits | Needs network. Peak Wi-Fi drop stops input | Device rules are contractual. Local-line design is extra | Device cache continues seating. Server CAS is the confirm of record |
| Exit and data | Notebook remains, unsearchable | Sheet ACL tied to a personal Google account | Export terms and term length must be in the quote | Month-to-month. Full CSV export is standard |
Read the table as operating risk, not as a price list. Paper looks free and prices 20:00 inventory as the real cost. A form produces the feeling of “we went digital” and holds no inventory lock. Foreign-engine OTA sync reduces double entry on the channels in the announcement. Shops that keep paper and manual LINE still fail the sentence “we prevent double-booking.” Rezabo’s 1,900 THB is the ledger line. LINE Official Account plan fees (Japan Light ¥5,000 ex-tax, Standard ¥15,000 ex-tax; Thailand Pro ฿1,780) and payment rail cost sit on other rows.
4. The race is “confirm has several meanings,” not only simultaneous taps
Seating collisions are not only millisecond double-taps. What breaks first on the floor is the definition of “this table is taken.”
Paper information void. A docket-back note is invisible to every terminal until it is copied. During the copy, another host walks a party in. A single notebook cannot serialize concurrent read and write.
LINE receive mixed with confirm. Official Account “read” is not a seat lock. “Thank you for contacting us” is not a lock. A rich-menu tap is not even a chat post. If you believe a Reply API card can be sent without moving inventory, you have given the guest a “we got it” feeling and left the room unsold or double-sold.
More channels, longer collision window. Instagram DM, Google Maps messages, Hungry Hub or Eatigo package slots, phone, walk-in. If the book of record is still one notebook, every extra mouth lengthens the queue of writes. Eatigo-style daypart discounts and Hungry Hub-style prepaid packages have different dwell and different check averages from a la carte. Unless slot type is split on one grid, Friday 20:00 private rooms sell as “regular four-top” and “package eight-top” at the same time.
Combine-table side effect. Joining two two-tops to seat four destroys later two-top demand. On paper that is a pencil line. Remaining inventory is never recalculated.
The unit that stops this is not a reply template. It is a single confirm event on (table_id, start_at, end_at). Phone, LINE, and walk-in that do not pass that event do not exist.
5. The lock is a server lease, not a grayed-out screen
Product pages stop at “the floor map moves in real time.” Design the write exclusion first.
Pessimistic lock (short lease). The instant any terminal taps a slot, that table-time pair gets a write lease of about 15 seconds. Other iPads and the LINE intake mark it unselectable (gray). Idle 15 seconds auto-releases so you do not deadlock. Optimistic locking (commit, then return a conflict) fails in front of a guest after a host has already said “we have you.” Hall service cannot use that pattern.
Confirm versus hold. Phone input is editing. Inventory decrements only when name, covers, and start time land and the host confirms. A LINE inquiry must not block 90 minutes. Unconfirmed holds expire (same-day 15 to 30 minutes) and reopen.
Offline state machine. Bangkok shop Wi-Fi drops at peak. Floor-map read and walk-in “start seating” may continue from device cache. If confirm is last-write-wins on the device, two iPads will both confirm the same room when the link returns. Offline new bookings stay pending. Only the row that wins server compare-and-swap becomes confirmed. The losing terminal gets a conflict UI and nearby-table suggestions.
Audit log. Who confirmed which table at which second. On a collision-suspect night you do not argue from memory.
Floor and contract landmines
- Paper and iPad forever in parallel. If the pass notebook stays the book of record after go-live, transfer lag remains. Dual-run is week 1 only. From week 2 the paper is a read-only log or it leaves the floor.
- ได้ค่ะ as confirm. Using the same word for “we are checking” and “the seat is yours” is the primary ghost-hold cause. Confirm is a reservation-ID card only. Chat is for changes and same-day contact.
- Japanese-only UI. A Japan ledger dropped onto a Thai floor is unreadable. Default UI is Thai. Japanese is a GM-terminal toggle.
- Reading OTA sync as the whole fix. TableCheck × Hungry Hub sync (announced 18 June 2024) reduces double entry on those two channels. Official LINE chat, personal LINE, paper, and walk-in are out of scope. “Sync is free” and “one book of record” are different invoices.
- Hall iPads on guest Wi-Fi. Captive portal and bandwidth caps kill WebSockets. Put floor devices on a shop SSID or a 4G router.
- Device clock and Buddhist calendar. An iPad left on BE 2569, or a time zone still set to Tokyo, writes the 20:00 slot onto the wrong civil date. Confirm time of record is the server clock.
- Client-only lock. Graying a screen does not stop an old tab on another device or the LINE engine. The lease is issued by the server and always returned on TTL.
- Per-cover and channel-commission blind spots. A cheap monthly can couple to cover billing the month you fill. Busy Fridays export gross. Split ledger cost from demand-channel cost when you read a quote.
- Infinite lease on a crashed iPad. A phone call that never ends, or a tablet that dies mid-edit, must not hold a private room until close. Cap extend (example: 45 seconds) and force-release.
- Package and a la carte on one chair. A 90-minute prepaid package and a 150-minute banquet cannot share a row unless stay length is a first-class field. Engine sync that only matches date and covers still double-sells dwell.
6. Plan traps and first-year TCO
On sticker price, paper plus LINE wins. Add 20:00 leakage and the ranking reverses. Use section 1’s ~144,000 THB per month (~1.73 million THB per year) as “we did not stop anything.” Shops that actually enforce one book of record and a lock often leave residual leakage under 200,000 THB a year. The delta is recovered inventory, not software.
| Annual cost item | Paper + manual LINE | Form + sheet re-key | In-house LIFF + Messaging API | Rezabo flat ledger |
|---|---|---|---|---|
| Software / book | 0 | 0 to Google Workspace | Build 80,000 to 200,000 THB plus yearly upkeep | 3,500 × 12 = 42,000 THB |
| LINE / notify | OA free tier. No confirm artifact | Manual send after eyes-on. Push counts | Japan Light: 5,000 msgs, no overage. Month-end confirm cards stop | Ledger is flat. OA quota is the plan line |
| Re-key / confirm labor | Fri-Sat peak ~25 hours/month. ~27,000 THB/year | Eyes-on remains, so the gap is small | Engineer hours on webhook watch | Low |
| Residual leakage (year) | 1.4 to 1.7 million THB | No lock, stays high | Quality of the lock you built | Low if one book of record is enforced |
| Year-1 TCO shape | Leakage is the product: 1.7M+ | Build + leakage | Capex dominates year 1 | Flat 42,000 + OA quota + residual leakage |
Three procurement mistakes repeat.
- Free chat and a notebook are “reservation software cost zero.” They are not “table-inventory operating cost zero.”
- In-house LIFF can produce a guest path. Same-slot double-tap, Reply versus Push billing, offline
pending, and remaining-inventory recalc after a combine are then yours to own. - Usage-based engines and OTA packages swell fees and discounts in the months you fill. Compare a monthly with no cover line against a busy-month invoice and the ranking flips.
Do not over-claim Rezabo’s number. 1,900 THB per month is the unlimited reservation ledger. LINE Official Account message quota, PromptPay or card rail cost, and any POS connector are separate rows. Because there is no per-cover booking commission, Friday-Saturday cover growth does not inflate software OPEX. Against ~1.73 million THB of leakage, the ledger line is two orders of magnitude smaller. The cut appears only in shops that keep the lock. Leave paper as the book of record and you have bought a second notebook.
7. API and infrastructure constraints to kill before go-live
Sales language says “real-time floor.” Contracts are rate limits, idempotency, and clock source. Treat the following as due diligence.
| Constraint | Published or field rule (as of August 2026) | What breaks on a Bangkok floor |
|---|---|---|
| LINE default rate | 2,000 requests/second on most Messaging API endpoints | Rarely the first failure. Shared chatbot + agency blast + booking webhook still add up |
| Multicast | 200 requests/second | Birthday blast during Friday service starves confirm pushes on the same channel |
| Narrowcast / broadcast / insight | 60 requests/hour | A dashboard polling delivery stats every minute will 429. Cache. |
| Reply token | One-shot, short-lived (field reports ~30 seconds) | If slot search outlives the token, the confirm card becomes a push and starts counting against quota |
| Quota counting | Reply does not count. Push, multicast, broadcast, narrowcast do | Three pushes per booking (confirm, T-24h, T-3h) × 400 covers = 1,200 messages before marketing |
| Over-quota send | 429, message is not queued | Month-end Friday reminder job dies mid-run. Japan Light cannot buy overage |
| Webhook URL | One per channel | A second SaaS “takes over LINE” by overwriting the endpoint. Booking events vanish |
| Phone from profile API | Not available | You cannot backfill a guest mobile from LINE profile. Collect it on the LIFF or ID token |
| Rich-menu URI tap | No replyToken |
Confirm and remind are push unless you still hold a chat session token |
| Lease TTL | Shop-defined; 15 seconds default | Phone hold needs extend-on-keystroke, hard cap ~45 seconds. Infinite lock = crashed iPad owns the room |
| Idempotent confirm | Required, not optional | LINE retries webhooks. Hosts double-tap. Same reservation ID must decrement inventory once |
Thailand and Japan do not share an Official Account price list. Quote the country of the OA.
| Plan (2026) | Japan (LYC) | Thailand (LINE for Business TH) | Trap for a reservation shop |
|---|---|---|---|
| Free / Communication | ¥0, 200 messages | ฿0, 300 broadcasts | Cannot buy overage. Send fails. Confirm cards die first. |
| Mid | Light ¥5,000, 5,000 msgs, no overage | Basic ฿1,280, 15,000 msgs, ฿0.10 extra | Japan Light is treated as “we can burst.” It cannot. |
| High | Standard ¥15,000, 30,000 msgs, then ¥3 / ¥2.50 | Pro ฿1,780, 35,000 msgs, ฿0.06 extra | Safe default for transactional push plus a weekly broadcast. |
| Oct 2026 Japan | LY structure change effective 1 October 2026 | Unchanged by that JP notice | Re-quote Q4 message budgets. Do not lock a 12-month agency retainer on July unit prices. |
CAS and idempotency belong in the confirm path, not in a wiki. The confirm API uses If-Match or a version number. Duplicate POSTs (LINE retry, host double-tap) decrement inventory once. Webhooks arrive late and twice.
type SlotState = "open" | "editing" | "held" | "confirmed" | "released";
interface SeatLease {
tableId: string;
startAt: string; // ISO-8601, Asia/Bangkok
endAt: string;
holderDeviceId: string;
leaseUntil: string;
version: number;
state: SlotState;
}
export async function acquireLease(input: {
tableId: string;
startAt: string;
endAt: string;
deviceId: string;
ttlMs?: number;
}): Promise<SeatLease> {
return rezabo.inventory.lease({
...input,
ttlMs: input.ttlMs ?? 15_000,
strategy: "pessimistic",
extendMaxMs: 45_000,
});
}
export async function confirmSeat(input: {
lease: SeatLease;
reservationId: string;
covers: number;
channel: "phone" | "line" | "walkin" | "ota";
idempotencyKey: string;
}): Promise<{ ok: true; version: number } | { ok: false; code: "CONFLICT" | "LEASE_EXPIRED" }> {
return rezabo.inventory.confirm({
...input,
ifMatch: input.lease.version,
});
}
LINE and payment processors will redeliver. Persist the event id before you move a slot.
{
"destination": "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"events": [
{
"type": "postback",
"replyToken": "nHuyWiB7yP5Zw52FIkcNy1",
"source": { "type": "user", "userId": "U4af4980629" },
"timestamp": 1774000800000,
"postback": {
"data": "action=confirm&slot=VIP_ROOM_SUK_08&start=2026-08-21T20:00:00+07:00"
},
"webhookEventId": "01JJ8K2N8X7Q3M0P1R2S3T4U5V",
"deliveryContext": { "isRedelivery": true }
}
]
}
SQL compare-and-swap is the book of record, not the iPad. Comment the intent in block comments so a DBA does not “simplify” the predicate.
BEGIN;
UPDATE table_slots
SET
status = 'confirmed',
reservation_id = $1,
version = version + 1,
confirmed_at = now() AT TIME ZONE 'Asia/Bangkok'
WHERE table_id = $2
AND start_at = $3
AND end_at = $4
AND status IN ('open', 'editing', 'held')
AND version = $5
AND lease_until > now();
INSERT INTO reservation_audit (
reservation_id, table_id, start_at, actor_device_id, channel, action
) VALUES ($1, $2, $3, $6, $7, 'confirm');
COMMIT;
If UPDATE returns zero rows, do not insert a second reservation. Return the conflicting id and suggested neighbors to the client.
mutation ConfirmSeat($input: ConfirmSeatInput!, $ifMatch: Int!) {
confirmSeat(input: $input, ifMatch: $ifMatch) {
reservationId
tableId
status
version
conflict {
existingReservationId
overlappingChannel
suggestedTableIds
}
}
}
Hall iPad truth is a WebSocket; on disconnect, short-cycle resume. Do not put those sockets on the guest VLAN. Fail over to 4G at the shop router. Display the device clock. Never confirm from it. Messaging API incidents on 28 July 2026 (LIFF and Login included) and 4 August 2026 are on the LINE Developers news feed. A booking path that is “LINE or nothing,” with no local seating cache, stops walk-ins when the platform is dark.
Silent overwrite on sync failure is a defect. Show the table, the conflicting reservation id, and a neighbor. An implementation with no audit log leaves only a Friday-night blame argument.
8. Freeze one book of record in three weeks
Do not change weekday lunch, terrace, and the private room on the same night. The floor will return to paper.
- Week 1. iPad is the write original. Paper is a read-only copy. Morning briefing drills three actions only: new, move, cancel. Load every existing advance booking at cutover and reconcile page counts against the diary. Dual-run ends when the counts match, not when the vendor leaves.
- Week 2. Remove the pass notebook. Open LINE rich-menu intake against live empty slots only. Ban ได้ค่ะ as a seat guarantee. Confirm is card issuance only. Personal LINE and Instagram become “please use the official booking mouth,” never a second confirm path.
- Week 3. Walk-ins seat on the same grid. Combine-two-tops rules (do not destroy remaining two-top demand) follow on-screen suggestions, not a pencil line. After Friday close, read the audit log for lock contention (attempted collisions that the lease already stopped).
Scope first to Friday-Saturday private rooms and parties of four or more. After 90 days near zero collisions, extend to terrace and counter.
9. Questions owners ask before they retire the diary
Q1. If the internet drops, do we lose the book? Current service plus the next 72 hours stay on device cache for display and seating. Confirm of record is server approval. If two iPads both look “confirmed” on the same room while offline, reconnect returns one of them as a conflict.
Q2. Can Thai hosts run this without English or Japanese? Default UI is Thai plus pictograms. Japanese stays on the GM terminal toggle. Ship Japanese-only and the floor goes back to paper.
Q3. How do we move the paper book that is already full? Load date, table, covers, phone, and notes in full at cutover, then match remaining page counts. Partial typing that leaves two books of record is the failure mode you are buying the product to leave.
Q4. Will phone intake put a guest on hold? Target about 15 seconds with number autocomplete and chips for tonight / four covers / private room. While the lease is held, other terminals cannot take the slot, so input contention does not happen.
Q5. Who decides to join two two-tops into a four? The screen shows combine candidates and the effect on remaining inventory, then someone confirms. A pencil line on paper never records that a later two-top just disappeared.
Q6. What about personal LINE and Instagram? House rule: no seat guarantee on personal accounts. DMs only redirect to the official booking mouth. A third write mouth deletes the meaning of the lock.
10. Metrics that prove the 20:00 slot is actually exclusive
A go-live that only measures “collisions this month” will declare victory while ghost holds and untranscribed vacancies keep leaking. Track the four layers from section 1 on the same dashboard.
| Metric | Paper + manual LINE | One locked ledger | Why it matters |
|---|---|---|---|
| Hard collisions / month | 1 to 3 on Friday-Saturday rooms | 0 | Door turnarounds and apology cost |
| Ghost holds still open at 18:00 | Common; chat ได้ค่ะ with no expiry | 0 (TTL 15 to 30 min) | Empty tables that look “full” on the book |
| Untranscribed phone notes | 4 to 8 per peak week | 0 (phone writes the grid) | Physical vacancy the book cannot sell |
| Walk-in refusals while a slot was open | Hosts guess from a smudged page | Near 0 if the terrace iPad reads live | Over-defense is silent leakage |
| Lock-contention events (lease denied) | Invisible | Visible in the audit log | Proof the mutex is working |
| Dual books of record after week 2 | Default | 0 is the cutover condition | Direct control |
Conclusion
The limit of paper ledgers and manual LINE booking is not that they are analog. It is that “confirmed” is split across chat, notebook, and speech, and Friday 20:00 private rooms have no exclusion. Driving hard collisions from two to zero does not return 150,000 THB if ghost holds and untranscribed vacancies remain. Make one book of record for table-time inventory. Put phone, LINE, and walk-in on the same confirm event. OTA sync and a Google Form help only when they attach to that book.
Rezabo is built around a table × time grid, a short write lease, LINE intake against live empty slots, and on-site Bangkok cutover of the existing diary. 1,900 THB per month is the ledger flat fee. Message quota and payment rails are other rows. A floor-plan demo is the right first check: can this room sell twice at Friday 20:00, or can it not.
Bangkok Restaurant No-Show Prevention Checklist (15 Rules)
An operational field guide for dining venues in Thailand: sync kitchen prep strictly with dynamic PromptPay QR deposits and automated LINE reminders to eliminate food waste.
- Strict synchronization of Friday 16:00 kitchen prep with paid bookings only
- Dynamic QR verification replacing static slip matching and slip forgery
- High-retention refund deadline rules and seamless tourist card routing
Delivered instantly via LINE Official Account (Instant 3-sec access)