Back to all case studies

Case study • Hospitality & Travel Technology

AI guest experience platform, zero to production in 26 days

Hostigram lets a host load everything they know about a property, event or group, print a QR code, and let guests get instant answers in their own language with no app and no login. Praxail was brought in to take an MVP that proved the idea and turn it into a business that could actually be sold and operated. The architecture, the data model, the pricing and packaging, and the legal and compliance layer were all redesigned from zero. Twenty six days later it was live in production, orchestrating more than twenty integrated services behind a single streaming interface, and it has been running there since.

26 days
Zero to production
20+
Integrated services
2,500+
Automated tests
Pen tested
Security & compliance
TypeScript
Nuxt 4
Cloudflare Workers
Neon Postgres
pgvector
Claude
Stripe
Live in production
Zero to production in 26 days

What this build proves

Most AI work stops at a prototype that demos well and cannot be sold. This is the job of getting from that prototype to a commercial product running in production.

An MVP turned into a business

The prototype proved people wanted it. It could not charge anyone, protect anyone, or survive a busy weekend. We rebuilt the architecture, the data model, the pricing and packaging, and the legal and compliance layer from zero, and had it in production in twenty six days.

Twenty services, one coherent product

Models, database, payments, authentication, email, storage, queues, support and analytics all have to agree on the same truth at the same moment. Holding that together without it becoming brittle is the actual engineering work, and it is where most AI products quietly fall over.

Safe enough to put in front of the public

Penetration tested, isolated inside the database rather than by convention, and built against GDPR and UK privacy law with export, erasure, retention and a data processing agreement in the first release rather than a later one.

Why we built this

Hosting is a one to many problem. One person holds the answers, and a crowd of people all need them at the same time, in different languages, at the worst possible moment.

Scan and ask, with nothing to install
A guest points a phone at a QR code or opens a link and starts a conversation in the browser. No app, no account, no password. Every visitor gets their own session, so one sticker on a fridge does not put an entire building into the same chat.
Knowledge in, from whatever the host already has
Typed notes, uploaded documents, photographed signs, or a link to a listing page. An extraction pipeline turns each of those into candidate question and answer pairs, and nothing becomes answerable until the host approves it.
Answers in the guest's own language
Replies mirror the language the guest writes in, falling back through the space setting, then the browser locale, then English. A verification pass catches the residual mismatches and corrects them before the guest ever sees them.
Branded, embeddable, and multi surface
Custom colours and logo, a shareable link, a printable QR code, and an embeddable widget that drops onto the host's own website as either a bubble or an inline panel.
What the host learns from it
Per space and aggregate analytics, full session transcripts with the retrieval trail behind each answer, guest notes and reply ratings, and a review queue for every question the system could not answer confidently.
Two ways to buy, one entitlement system
Recurring subscriptions for hosts who run continuously, and time-boxed passes for a single wedding or trip. Both resolve into one entitlement check that runs on every guest message and every attempt to take a space live.

Where Praxail came in

The starting point

A working MVP. It proved the idea and it convinced people, which is exactly what an MVP is for. What it could not do was take money, isolate one customer from another, survive a busy weekend, satisfy a data protection request, or answer the question every AI product eventually has to answer: what stops the model bill running away when anyone with a phone camera can start a conversation.

What we rebuilt

Everything under the idea, redesigned from zero. The architecture and data model, the retrieval and prompt layer, the security posture, the commercial model itself with its plans, passes and entitlement rules, and the legal and compliance layer down to the privacy policy, terms, data processing agreement and retention schedule. Praxail is the engineering and platform side of Hostigram, so the product decisions and the engineering decisions were made in the same room.

Codebase
~95,000 lines
Rebuilt by
Praxail, end to end

What shipped

Every figure below is counted from the repository, not estimated. This is the surface area that replaced the MVP in under a month.

26
Days to production
Rebuild to live release
20+
Integrated services
Coordinated as one product
2,500+
Automated tests
Across 21 subsystems
95k
Lines of code
Excluding generated files
164
API endpoints
Across the whole platform
53
Database tables
14 forward-only migrations
22
Background handlers
Queue jobs and scheduled sweeps
3
Isolated environments
Separate data and infrastructure

How a guest question gets answered

A guest scans a sticker on a fridge and expects an answer before they lose patience. Every decision in this path exists to protect that moment.

1. Guest arrives with no identity

A QR scan or shared link resolves to exactly one host space. A per visitor session is minted, access mode is checked (open, access code, or guest list), and the conversation is bound to that session alone.

Per visitor sessions • 3 access modes
Step 1

2. Five gates before anything is stored

Feature kill switches, rate limits, space lifecycle, billing entitlement, and input screening all run before the message is persisted or a model is called. A rejected turn never reaches the database and never costs anything.

Gates run first • Nothing stored on rejection
Step 2

3. Hybrid retrieval across three scopes

Two independent retrieval arms run over the host's knowledge, one semantic and one keyword, and are fused into a single ranked list. Three scopes fuse in the same query: the space's own knowledge, platform level guidance, and anything the host attached to this specific guest.

Semantic + keyword • pgvector and full text search
Step 3

4. Prompt assembly from versioned modules

Space context, the retrieved knowledge, any itinerary, the host's chosen tone, and language instructions are assembled from prompt modules that live in the repository like any other code, so a wording change is a reviewable diff.

7 versioned prompt modules • Host editable tone
Step 4

5. Generation, with a second provider behind it

A fast primary model generates the reply, with an automatic fallback to a second provider on failure. If both are unavailable the guest gets a calm fixed response and the host gets an escalation, never a broken screen.

Primary plus fallback • Bounded tokens and time
Step 5

6. Tokens stream while the work continues

Text starts appearing as soon as the model produces it. Everything that does not have to happen first is pushed behind the stream, so the guest is never waiting on bookkeeping.

Server sent events • Split at the stream boundary
Step 6

7. Accounting happens afterwards

Once the stream closes, a background job records the turn, the retrieval trail, the cost ledger entry and any escalation. If the queue is unavailable the job runs inline instead, so no turn is ever lost to an infrastructure hiccup.

Queue with inline fallback • Idempotent by design
Step 7

Tech stack

TypeScript
Strict end to end, with a shared contract layer so a renamed error code is a compile error, not a runtime surprise.
Nuxt 4
Server rendered client and file routed API in one application, from marketing site through to admin console.
Cloudflare Workers
Edge runtime, object storage, key value cache, queues and cron triggers. One deployed artefact.
Neon Postgres
Primary database, vector store and search index in one system, with row-level security doing the isolation.
pgvector
Semantic retrieval arm, indexed alongside Postgres full text search for the keyword arm.
Claude
Primary generation model, with a second provider wired as an automatic per request fallback.
Stripe
Subscriptions and one off passes, with a catalogue defined in code and reconciled into Stripe by script.
One deployable artefact

Server rendering, the API, the queue consumer and the scheduler all ship as a single edge worker. One deploy to reason about, one place to configure, and no internal service mesh to secure for a product this size.

Edge deployed
3 isolated environments

Twenty services, held together at speed

The hard part of a platform like this is never any single piece. It is keeping ten independent services agreeing with each other, under load, without the whole thing turning into something nobody can safely change.

How the speed is protected

The work is split at the point the guest starts reading
Only the gates, one write and one retrieval query sit in front of the first visible word. Everything else, the transcript, the cost ledger, the retrieval trail, the escalations, happens after the stream has already started. The guest never waits on bookkeeping.
One database doing three jobs instead of three databases
Postgres is the primary store, the vector index and the keyword search index at once. Two retrieval arms fuse inside a single query, which removes an entire class of cross-system latency, drift and failure that a separate vector database would have introduced.
Deployed to the edge as one artefact
Rendering, the API, the queue consumer and the scheduler ship together and run close to the guest rather than in one region. One deploy to reason about, one configuration surface, and no internal network hops between services on the hot path.
Every layer degrades instead of failing
The model provider has a fallback. The queue falls back to running inline. Kill switches fail open so a cache blip cannot take the product down, while runtime settings fail safe to code defaults. Nothing in the request path has a single point of failure that ends the conversation.
Tuned under load without a deploy
Forty operational settings, from timeouts to cost caps to retention windows, are editable from the admin panel and take effect in seconds. Performance tuning during a real traffic spike does not require a release.

The integration surface

Every one behind a seam with a working local stand-in, so the whole product runs on a laptop with no credentials and no spend.

Intelligence

Claude
OpenAI
pgvector
Postgres full-text search

Data and runtime

Neon Postgres
Cloudflare Workers
R2 storage
Workers KV
Queues
Cron Triggers
Drizzle

Commerce

Stripe Billing
Stripe Checkout
Customer Portal

Identity and access

Better Auth
Google OAuth
Turnstile

Communication

Resend
Zoho Desk

Observability

Sentry
Umami
Workers Logs

Six problems worth naming

The parts of this build that separate a demo from a product you can charge money for and leave running unattended.

An AI bill that cannot run away

The problem

Every guest message costs money at a model provider, and anyone who walks past a QR code can send one. There is no login to throttle behind and no account to charge. A busy evening is not abuse, but it is still an invoice.

What we did

Spend is metered per operation into a ledger, with a daily rollup for cheap global reads. A two layer circuit breaker sits in front of generation: a per host daily cap evaluated in that host's own timezone, and a platform wide kill switch. Above that sit rate limits keyed to both session and network, bounded output and generation time, retrieval instead of context stuffing, and a short circuit that answers without calling a model at all when a space has no knowledge loaded.

The worst case cost of an anonymous public surface is a known number rather than a discovery at the end of the month, and one busy host cannot degrade the platform for everyone else.

Isolation that survives a coding mistake

The problem

Retrieval fuses three knowledge scopes in a single query. The obvious implementation adds a filter clause and hopes nobody ever forgets one. A refactor that drops a clause on a query still returning plausible results is a silent cross customer leak that ordinary tests do not catch.

What we did

Isolation moved out of application code and into the database. Three restricted database roles, thirty six tables under forced row-level security, fifty one policies, and session variables set inside each transaction. The per guest scope in particular carries no application filter at all: an unbound session reads zero rows because the policy says so. The application ownership guard stays as a second layer, and a custom lint rule fails the build if a handler omits it.

A host's private information cannot surface in another host's chat even if a query is written wrong, and that guarantee is enforced by infrastructure rather than by remembering.

Fast enough for someone stood at a locked door

The problem

The full work for one message is five gates, message persistence, two retrieval arms plus a fused ranking, prompt assembly, generation, and then persistence of the turn, the cost ledger, the retrieval trail and any escalation. Doing all of it before responding puts seconds of bookkeeping in front of the first visible word.

What we did

The turn was split at the stream boundary. Only what must precede generation stays in front of it. Everything else moves to a post stream job, queued where possible and executed inline where not, with an idempotency key on every job so at least once delivery cannot produce a duplicate receipt or a duplicate message.

Text appears while the system is still doing its accounting, and no message is ever lost to a queue outage.

Making a language model behave like a product

The problem

A model that is right most of the time is a demo. A guest asking where the spare key is needs an answer that is grounded in what this specific host wrote, in the language they asked in, that refuses cleanly when it does not know, and that cannot be talked into ignoring its instructions by someone who finds it funny to try.

What we did

Prompts were treated as engineering artefacts rather than text pasted into a settings box. Seven versioned prompt modules live in the repository and are imported like any other code, so a wording change is a reviewable diff with an author and a history. Each reply is assembled in layers: host context, the retrieved knowledge, any itinerary, the host's chosen tone, and language instructions, composed at request time rather than concatenated by hand. Behind that sit two independent verification passes: a language check that regenerates once on a confident mismatch, and a two tier input screen that runs a cheap deterministic pass on every message and escalates to a classifier only on suspect input.

Answers stay inside what the host actually wrote, refusals turn into a recorded escalation instead of an invention, and a wording change can be reviewed, shipped and reverted like any other code change.

Twenty services that must never disagree

The problem

A single guest message touches models, database, vector search, cache, queue, object storage and the billing system. One host action can touch payments, email, entitlement and the scheduler. Each of those can fail independently, respond twice, respond out of order, or respond late, and every one of those failures is visible to a paying customer.

What we did

Every external service sits behind a seam with a working local stand-in, so the entire product runs with zero credentials and any vendor can be swapped without touching business logic. Payment webhooks reconcile against live provider state rather than replaying event bodies, which makes duplicated and out of order delivery a non-event. Background jobs carry explicit idempotency keys so at-least-once delivery cannot produce a duplicate receipt. Server rendering, the API, the queue consumer and the scheduler all ship as one artefact, so there is one deploy to reason about and no internal service mesh to secure.

A vendor outage degrades one capability instead of taking the product down, and the whole system can be run end to end on a laptop with no accounts and no spend.

Messy documents into knowledge you can trust

The problem

Hosts arrive with a PDF house manual, a photograph of a laminated sign, or a link to a page that only renders with JavaScript. Feeding raw extracted text to a model means answering from stale phone numbers and superseded rules with nobody having checked. Fetching arbitrary user supplied URLs from a server is also a well known attack vector.

What we did

A staged asynchronous pipeline with a human gate. Uploads land in object storage, an extraction job pulls text including vision based reading of images, a chunking stage fans out one generation job per chunk, and each produces candidate question and answer pairs into a review queue. Nothing becomes answerable until the host approves it. URL fetching runs through a guarded fetcher with request forgery protections and a rendering fallback that sits behind its own kill switch.

A host uploads a house manual and reviews a list of suggested answers, rather than hoping the system read it correctly and finding out from a guest.

Twenty six days, six phases

From the first line of the rebuild to a tagged production release, then a further month of post-launch depth. Every phase is anchored to real commits.

Phase 1
4 days

New foundations and the commercial front door

The MVP was set aside rather than extended. New edge runtime, strict TypeScript, database layer, structured logging with personal data redaction, error tracking, health endpoint, CI, and the custom security lint rule. The brand aligned marketing site, design token system, help centre and SEO plumbing shipped in the same window.

Phase 2
6 days

Identity, tenancy and the guest pipeline

Passwordless authentication, space and knowledge management, and row-level security with the restricted role model. Then the entire guest surface: token gated chat, the guest database role, the model adapter with fallback, hybrid retrieval, and the first cost caps and rate limits.

Phase 3
5 days

Commerce and background work

Background jobs and scheduled sweeps moved into the same worker, then the full billing system: catalogue defined in code, checkout and customer portal, reconciling webhooks, trials, and the entitlement gates that every guest message passes through.

Phase 4
3 days

Documents, languages, analytics and privacy

Five milestones in three days. Document ingestion with the review queue, a second language with mirror first resolution and verification, host analytics with session detail and escalations, audited admin impersonation, feature flags, then data subject export and erasure with retention sweeps.

Phase 5
8 days

Hardening and production launch

Milestone follow ups, the launch documentation set, and a deliberate consolidation of the migration history into a clean baseline before any production database existed. Ends at the first production release, twenty six days after the first commit.

Phase 6
27 days

Post launch depth

The embeddable widget, guest input screening, an abuse dashboard, bot protection, the support console, a typed notification system, platform wide and per guest knowledge scopes, itineraries, plan limits with tiered analytics, and a full end to end QA pass.

Security, privacy and compliance

A guest surface that anyone can reach without an account changes what the security model has to do. This is the part an MVP gets to skip and a business does not.

Penetration tested

The public guest surface was tested adversarially rather than assumed safe, alongside a full end to end quality pass across the host, operator and billing surfaces before it carried real traffic.

Isolation the database enforces

Thirty six tables under forced row-level security with fifty one policies and three restricted database roles. One customer cannot read another even if application code is written wrong, because the guarantee does not live in application code.

GDPR and UK privacy law

Data subject export and erasure with a thirty day grace period and re-authentication, nine enforced retention windows, a published privacy policy, terms, data processing agreement and a do not sell notice. Written into the first release, not retrofitted.

Guests give up nothing

No account, no email, no payment data. Network addresses are hashed and never stored raw, and third party analytics are deliberately absent from the guest surface entirely, because a guest never consented to anything.

Security enforced by the build

A custom lint rule fails the pipeline if any endpoint that changes data omits its ownership guard. Security is a compile step, not a review checklist item that a busy week can skip.

Nothing ships on a red pipeline

Formatting, linting, type checking, the full test suite and a production build all gate every push. Migrations run before code, and the deploy job will not start unless every check passed.

Thinking about building something like this?

If you have a prototype that demos well and cannot yet be sold, the gap is rarely the model. It is the architecture underneath it, the commercial model around it, and the security and compliance work that turns it into something you can put in front of the public. That gap is what we close.