We turned four cannabis stores and 31 screens into one system we run remotely
An Electron POS check-in app wired to POSaBIT, a Fire TV signage fleet that updates itself every day, a web menu and case-card designer with approvals, and a queue backend that keeps it all in sync. We built it, then we run it.
The problem
A four-store cannabis retailer was stitched together from disconnected tools. Customer check-in, the lobby queue, the menu boards, and the printed case cards on the shelves were all separate jobs, each done by hand at each store. POSaBIT held the inventory and loyalty data, but nothing pulled it into the front of house automatically. Signage on the TVs meant someone physically touching each screen. A price or product change on the floor meant re-typing it in three places. The owner needed the whole front-of-house to behave like one system, kept current from a distance, without hiring a tech team per store.
What we built
The modules of the system.
Check-in kiosk (Windows POS app)
An Electron desktop app that runs fullscreen on a touchscreen at each store. A customer scans the back of their driver's license (AAMVA PDF417), and the app pulls name, date of birth, and address straight off the barcode, looks them up, links the account, and drops them in the POSaBIT queue. Returning customers can be checked in automatically the moment they scan. It is offline-first: a local SQLite database holds the full customer list (around 20k records, synced in the background) so check-in never stalls when the internet hiccups, and any check-ins done offline sync back when the connection returns.
Fire TV signage fleet
Thirty-one Amazon Fire TV Sticks across the stores, running in two modes: a live queue WebView in the lobby and full-screen offline signage on the menu and promo boards. The whole fleet is deployed and updated over ADB through a VPN into each store network, and the kiosk app ships silent daily auto-updates so a new build rolls out to every screen without a truck roll or a staff member climbing a ladder.
Queue backend and admin
A full-stack web app (React 19 + Express + Prisma/PostgreSQL) that runs the lobby experience: self-service kiosk check-in by phone (loyalty) or name (guest), a live TV display with a marketing ticker, and an admin console where staff call the next customer, manage the team with role-based access, theme each lobby, and wire up POSaBIT per location. Real-time updates flow over Socket.io so the display and the admin stay in lockstep.
CaseCard menu and case-card designer
A web app for designing the printed case cards and menu boards on the shelves. It pulls live inventory from POSaBIT (dual API: v1 for product detail, v3 for tags and lazy loading), parses THC/CBD and pricing into clean fields, and runs templates through a draft-then-approve flow so a manager signs off before anything prints. Teams, locations, and roles keep each store editing its own shelves.
Live menu display
A separate TV menu app that renders POSaBIT inventory into several board layouts (card grid, split screen, table) with store branding, filtering, and a print path, so the on-screen menu and the printed materials read from the same source of truth.
How it fits together
POSaBIT is the system of record for inventory, customers, and loyalty. Everything else reads from it and writes back only where it should. At each store, the Electron kiosk talks to the POSaBIT v3 API with an integrator token plus a per-venue token, keeps a local SQLite mirror of customers for offline check-in, and pushes check-ins into the POSaBIT queue. Every five minutes it heartbeats fleet stats (check-in counts, machine health) up to the queue web app, fail-silent, so a telemetry problem can never block a customer at the counter. The queue backend (Express + Prisma on PostgreSQL) serves the lobby kiosk, the TV display, and the admin console, caches POSaBIT queue reads to avoid hammering the API from every screen, and broadcasts live changes over Socket.io. CaseCard and the menu display both read POSaBIT inventory through the same dual-API pattern and turn it into print-ready cards and on-screen boards. The 31 Fire TV Sticks are managed as a fleet over ADB through a VPN into each store, and the desktop app auto-updates daily off GitHub releases. The result: a price or product moves in POSaBIT once, and the kiosks, the boards, and the printed cards all follow, with the owner managing the whole thing from one place instead of store by store.
Under the hood
The decisions that mattered.
Offline-first check-in so the line never stops
A retail counter cannot wait on a network round-trip. On first launch the kiosk does a full background sync of roughly 20k customers into a local better-sqlite3 database, then runs incremental syncs every 15 minutes after that. Lookups, license scans, and check-ins all hit local storage first, so the app stays instant even when the store WiFi drops. Check-ins made while offline are stored in a local queue and replayed to POSaBIT once the connection returns. The driver's-license path is the workhorse: the app parses the AAMVA PDF417 barcode on the back of the license, extracts name, DOB, and address, links or creates the customer, and (for returning customers) can complete the check-in automatically on scan. Telemetry heartbeats run on a separate, fail-silent path so they can never interfere with the counter.
A queue backend hardened for many always-on screens
Dozens of TVs and kiosks poll the same backend all day, every day, so the server is built to refuse to fall over rather than try to do everything. A backpressure middleware caps in-flight requests (sized at about 5x the Prisma pool) and returns a 503 with Retry-After instead of collapsing the database connection pool under a burst. The choice was deliberate: the bottleneck here is I/O wait on Prisma queries, not CPU, so an in-flight counter is the right signal rather than event-loop sampling. POSaBIT queue reads are cached for ~30 seconds so ten displays in one lobby produce one upstream call, not ten. Socket.io connections are capped at the handshake with an atomic per-IP reservation, and per-message size is capped at 100 KB (far below the 1 MB default) since real messages are under 1 KB. None of this is glamorous, but it is what keeps 31 screens stable without a babysitter.
Admin actions that stay a display concern, not a data write
When a staffer marks a POSaBIT-sourced customer as serving or dismisses them from the lobby display, that is a signal to the person waiting, not a change to the source of record. So those actions live in a transient in-memory overlay keyed by location and customer, folded into every queue read, hard-capped by age, and garbage-collected once POSaBIT stops returning that customer. They are intentionally lost on a backend restart. This keeps POSaBIT as the single source of truth, avoids writing display-only state back upstream, and means a deploy is safe at any hour. It is a small decision with a big payoff: the lobby never disagrees with the POS.
Approve-before-it-prints on the shelf
The case cards and menu boards customers read on the shelf are compliance-sensitive, so CaseCard does not let anyone print straight from a draft. Inventory comes from POSaBIT via a dual API (v1 for full product detail, v3 for tags and lazy loading), and the app parses THC/CBD and pricing into structured fields so a card is built from clean data, not copy-paste. Templates run through a draft-then-approve flow, scoped by team, location, and role, so a manager signs off before a card hits the print queue. One product update in POSaBIT flows into the cards and the boards together, which kills the old habit of re-typing the same price in three places and getting one of them wrong.
Release log
What we shipped.
First Electron check-in app: multi-venue support, driver's-license scanning, and guest entry, wired to the POSaBIT queue.
Driver's-license-number lookup and automatic account linking on ID scan, plus a 3 AM auto-restart and on-screen version display to keep unattended kiosks healthy.
Returning customers get checked in automatically on scan, including straight from the home screen, with a hardened barcode parser.
Full-stack queue system shipped: real-time Socket.io display, admin console with role-based access, media library, playlists, registered screens, and signage analytics, with backpressure and connection caps for always-on TVs.
Web menu and case-card designer with POSaBIT dual-API inventory, THC/CBD and pricing parsing, templates, and a draft-then-approve print flow.
Continuous production updates (latest v2.1.8, May 2026): refined check-in copy, expanded blocked-word filtering, and store roster changes pushed silently to the whole fleet.
The outcome
Four stores and 31 screens now run as one system that the owner manages from a distance. Check-in is fast and works even when the store network does not. Menu boards and printed case cards read from the same live POSaBIT data, so a price or product changes once and follows everywhere, with a manager's sign-off before anything prints. The Fire TV fleet updates itself daily over ADB and VPN, so new builds reach every screen without a store visit. We did not just hand over software. We built the front-of-house operating system and we keep running it.
Put your business on autopilot