
SpotBooker
Book rooms, auto-release no-shows, never waste a space
Hey IH π
Post #3 of my build in public journey with SpotBooker. Today I'm breaking down the feature that makes the whole product work: automated check-in.
## The problem
In most offices, 30-40% of booked meeting rooms are never actually used. People book "just in case," meetings get cancelled last minute, or someone just forgets. The room stays blocked all day while others wander around looking for a free space.
I lived this in every company I worked with. The calendar shows zero availability, but half the rooms are empty. It's absurd.
## The solution
Simple concept: if you book a room, you have to prove you're there. If you don't, the room frees itself.
When someone makes a booking, they get a check-in window (configurable per company β usually 10-15 minutes after the booking starts). They can confirm their presence three ways:
1. QR code scan β each room has a unique QR code posted on the door
2. NFC tap β for offices with NFC tags
3. Email confirmation β one-click link for remote-first teams
If nobody checks in before the window closes, the booking is automatically cancelled and the room appears as available again.
## The technical implementation
QR codes are signed, not just random IDs.
Each QR code is generated with HMAC-SHA256 using a secret key + spot ID + timestamp. When scanned, the server validates the signature before accepting the check-in. This prevents someone from just guessing room IDs or reusing old codes.
Rate limiting prevents abuse.
Each spot is limited to 10 check-in attempts per minute. This stops brute-force attacks and also catches edge cases like a broken scanner repeatedly hitting the endpoint.
The release job runs on a queue.
A scheduled Laravel command runs every minute, looking for bookings that:
- Started more than X minutes ago (configurable check-in window)
- Have no check-in recorded
- Haven't been manually cancelled
These bookings are marked as "no-show" and the room status flips to available. A notification goes to the original booker so they know what happened.
Real-time updates via broadcasting.
When a room is released, other users see it instantly without refreshing. I use Laravel's event broadcasting with a simple polling fallback for older browsers. The calendar UI (FullCalendar 6) refetches events when it receives the "room released" event.
## Edge cases I had to handle
Multiple bookings in sequence. If Room A has a 9am and 10am booking by different people, and the 9am person doesn't check in, the room should only be released until 10am, not the whole day.
Timezone hell. Companies have offices in different timezones. The check-in window has to respect the local time of the room, not the server time. I store everything in UTC and convert on display.
Partial attendance. What if 3 people are invited but only 1 shows up? For now, one check-in is enough to hold the room. Some customers asked for a minimum attendance threshold β it's on the roadmap but adds complexity.
Grace period after release. If a room is released at 9:15, I block new bookings for that slot for 2 minutes. This prevents a race condition where someone books it the exact moment the original person finally arrives.
## Results
For the companies using this feature actively, the impact is real:
- Rooms that used to show "fully booked" now have 20-30% more actual availability
- No-show bookings dropped significantly once people knew their rooms would disappear
- Facility managers finally have accurate data on real usage vs. booked usage
## What I learned
The check-in feature is what sells SpotBooker. Every other feature (search, recurring bookings, approvals) exists elsewhere. But "your rooms free themselves" is the hook that makes people pay attention.
Sometimes one well-executed feature beats ten mediocre ones.
What's the one feature that defines your product?
Hey IH π
Yesterday I introduced SpotBooker β my solo-built meeting room booking SaaS. Today I want to go deeper into the tech stack, because I know a lot of you are making these decisions right now.
## The stack
- Backend: Laravel 10 / PHP 8.1+
- Frontend: Vue 3.2 (Composition API) / Tailwind CSS 3.3
- Glue: Inertia.js (no separate API needed)
- Database: MySQL with multi-tenant architecture
- Cache & Queues: Redis
- Payments: Stripe via Laravel Cashier 14 with metered billing
- Email: Mailjet API v3
- Calendar UI: FullCalendar 6 (resource timeline)
- Charts: Chart.js 4
- Auth: Laravel Socialite (Google OAuth)
- i18n: mcamara/laravel-localization (4 languages)
- Hosting: AWS (S3, Redis, the usual)
- Build: Vite 4
## Why Laravel + Vue
As a solo founder, I needed to move fast without sacrificing quality. Laravel gives you so much out of the box β auth, queues, scheduling, mail, payments with Cashier. I didn't have to stitch together 15 different libraries to get a working backend.
Vue 3 with Composition API was a natural fit. The reactivity system is perfect for a booking app where availability changes in real time. Combined with Inertia.js, I get the SPA feel without maintaining a separate API layer. One codebase, one deployment.
Tailwind CSS was a no-brainer for speed. No time spent naming CSS classes or fighting specificity. The whole UI is consistent and responsive without a dedicated designer.
## The decisions I'm happy with
Inertia.js was the best decision I made. No REST API to maintain, no state management hell, no CORS issues. The frontend talks directly to Laravel controllers through Inertia. For a solo dev, this saves an enormous amount of time.
Laravel Cashier with metered billing handles the pricing model cleanly. The base plan includes 8 spots, and additional spots are billed as usage through Stripe. Cashier makes this almost trivial to implement.
Redis for everything β session, cache, queues. One service, three jobs. Queue workers handle email notifications, check-in reminders, and analytics event processing without blocking the main request.
FullCalendar 6 with resource timeline view. Building a calendar from scratch would have taken weeks. FullCalendar handles drag-and-drop, resource grouping, and timezone management. Worth every minute spent learning the API.
## What I'd reconsider
MySQL over PostgreSQL. MySQL works fine, but PostgreSQL's JSON support and full-text search would have been useful for the analytics engine and SpotFinder. Not a dealbreaker, but if I started today I'd go Postgres.
Mailjet. It works, it's reliable, but the API documentation is mediocre and the dashboard feels dated. I'd probably look at Resend or Postmark if starting fresh.
No TypeScript. I started with plain JS in Vue components for speed. Now with 50+ components, I occasionally miss type safety. Adding TypeScript retroactively is painful, so my advice: start w
1 Like
Comment
Hey IH π
I'm Mounir, solo founder of SpotBooker β a SaaS for booking and managing meeting rooms, desks, and shared workspaces.
I wanted to share my journey because I think a lot of indie hackers face the same wall I hit: building a solid product is the easy part. Getting people to find it is where things get real.
## What SpotBooker does
The core idea is simple: in most offices, up to 40% of booked meeting rooms sit empty. People reserve rooms "just in case," forget to cancel, or simply don't show up. The room stays blocked all day while colleagues wander around looking for a free space.
SpotBooker fixes this with auto check-in. You book a room, you have to confirm you're there (QR code, NFC, or email). If you don't check in within the configured window, the room is automatically released and available again.
On top of that:
- SpotFinder lets you search rooms by capacity, equipment, floor, availability β find the right space in under 2 minutes
- Approval workflows for sensitive spaces
- Recurring bookings
- Analytics dashboard showing real utilization data
- Public booking mode (great for coworking spaces)
- 4 languages built-in: English, French, Spanish, Portuguese
## The tech stack (for the nerds)
Laravel 10, Vue 3 with Composition API, Tailwind CSS, Inertia.js, Stripe via Laravel Cashier with metered billing, Mailjet for transactional emails, Redis for queues and caching, FullCalendar 6 for the timeline view. Hosted on AWS. Multi-tenant architecture on MySQL.
I built everything solo. It took way longer than I expected, but I'm proud of how solid the product turned out.
## The real challenge: acquisition
Here's the honest part. The product works well. Users who find it tend to stick around β retention isn't the problem. The problem is distribution.
For a long time, my only real acquisition channel was Capterra. It generates qualified leads, but being dependent on a single channel is risky.
So I started diversifying:
- SEO with a multilingual blog (4 languages, targeting room-booking and flex-office keywords)
- SaaS directory listings (SaaSHub, AlternativeTo, and a bunch of tech showcases like MadeWithLaravel)
- Starting outbound campaigns targeting LATAM markets where the pricing is very competitive
- And now, sharing the journey here on Indie Hackers
## Pricing
- Free plan: 2 spots, core features included
- Paid: starts at $19/month (annual) with 8 spots included, then $2/spot/month
- 14-day trial, no credit card required
## What I've learned so far
1. Building the product is not the hard part. I spent months perfecting features. I should have spent that time on distribution earlier.
2. Multilingual from day one was a good bet. Having 4 languages built-in opens markets that English-only competitors ignore.
3. Free plans work as a funnel, not as charity. The free tier gets people in the door, and the upgrade path is natural once they add more rooms.
4. Capterra is great but fragile. One channel = one point of failure.
## What's next
- Scaling content across 4 languages
- Testing outbound in LATAM
- Building in public here on IH
I'd love to hear from other B2B SaaS founders: what worked for you to break past the "great product, no traffic" wall?
Check it out: https://spot-booker.com
1 Like
Comment
About
I kept seeing the same problem in every office I worked with: meeting rooms booked but sitting empty all day. People reserve "just in case," forget to cancel, and the room stays blocked while others wander around looking

1 Comment
Congrats on the launch, looks solid. How are you currently thinking about acquiring early users and gathering feedback?