Naqvix logo
Book a Call
Back to Work
ES

Emerald San Ramon

BuildGrow

Emerald San Ramon: A Venue Website and the CRM That Runs the Building

Events & Hospitality — Event VenueSan Ramon, California, United Stateslive
Visit Website
Emerald San Ramon homepage hero — "A hall built for celebration" over a photograph of the venue dressed for a wedding, with Book Your Event and Book A Tour calls to action

The Challenge

Emerald San Ramon runs one room in San Ramon, California. Weddings, quinceaneras, corporate offsites, religious celebrations, conferences — the same hall, reconfigured, one event at a time.

That last detail is the whole problem. A venue with one room sells dates, not capacity. Every enquiry is really the same question: is that evening free, and will you hold it for me? Answering it reliably means the enquiry, the site visit, the calendar, the deposit and the final headcount all have to be one record. At Emerald they were not. Enquiries came in from the website and over the phone and then lived wherever the person who took them happened to put them.

The website had a second, quieter problem: the numbers on it were written into the code. Changing events hosted from 300 to 350, or adding a cuisine to the catering list, meant asking a developer. So it did not get changed.

And the venue had started selling tickets to its own public events, which is a different business from private hire. That needs a public page, real card payments, and a door on the night where a staff member has to know — in a few seconds, with a queue forming — whether the person in front of them has actually paid.

The Solution

We built two things on one database: the public website, and a CRM at /admin that the venue is actually run from. Twenty-one screens, grouped the way the work happens rather than the way the tables are shaped.

Pipeline. Leads land from the website, the chatbot, walk-ins, referrals and social, and move through New, Contacted, Proposal, Won and Lost. The same six leads render either as a table or as a drag-and-drop board, because reviewing a list and working a pipeline are different jobs. A won lead becomes a booking; bookings and site tours share one calendar, colour-coded so a pending hold, a confirmed event and a tour that nobody has phoned back are distinguishable at a glance.

Ticketing, and the part with money in it. Public events sell through Stripe Checkout. Three properties are worth naming, because each one is a real failure mode we designed against rather than a feature:

  • Only a signature-verified webhook mints a ticket. The handler reads the raw request bytes — parsing the JSON first reformats them and every signature check then fails, which is exactly the pressure that tempts someone into skipping verification. Without that check, anyone who learns the URL can post a fake paid event and print free tickets.
  • Idempotency is a unique-constraint violation, not a lookup. Stripe retries until it gets a 2xx. Checking whether an event was already handled and then inserting leaves a window where two retries both mint a ticket. Letting the database reject the duplicate closes it.
  • The last seat cannot be sold twice. A pending order holds no seat, so two buyers can both reach Stripe for the final ticket. The fulfilment transaction retakes a row lock and refunds the loser automatically rather than overselling the room. Choosing to refund a real customer is a worse outcome than a clean sale and a better one than a family arriving at a full hall.

Staff sell at the counter through the same records, so cash and card reconcile against one set of takings. At the door, a receipt code — typed or scanned — checks a guest in.

An AI chatbot the owner controls. The website chatbot answers from a plain-text brand knowledge field the owner edits in Settings. No prompt engineering, no redeploy: whoever runs the venue writes what is true, and the assistant says that. It also reads the venue's real availability rather than guessing at dates.

Content, without a developer. The headline numbers, catering menu, gallery albums, blog, site images and the notification address for enquiries all live in the CRM. Leave a number blank and the website hides that stat rather than printing a zero — the kind of small decision that stops a half-filled admin panel from embarrassing the business in public.

A help centre, written for staff. Thirty topics covering every screen in the menu, in the language of someone running a front desk rather than a developer. It also states plainly where the CRM cannot yet do something, so nobody discovers that with a customer standing in front of them.

The whole system is self-hosted on its own server: Docker, PostgreSQL, automatic TLS, nightly encrypted off-box backups.

Results

The site and the CRM have been live on their own infrastructure since August 2026, with certificates renewing themselves and every public form protected against bot submissions. Enquiries reach both the CRM and a staff inbox, and that path was verified with a real lead end to end before handover.

1,143 automated tests run against a dedicated database, which is what makes changing a payment path survivable rather than frightening.

One number worth reporting honestly, because it was a bug we caused and then found: warm image responses were taking 1.00 second each. A Docker volume had been mounted onto a cache directory the container did not own, so the application could not write to it and the framework silently disabled image caching — no error, no warning, just every request re-encoding the photograph from source. Fixing the ownership took warm TTFB to 0.013 seconds, roughly seventy-five times faster. It is in this write-up because a green test suite never would have caught it, and neither would a page that merely looked fine.

A second one in the same category: changing a password from inside the CRM returned a server error while all 1,143 tests passed, because every test called the service layer directly and nothing exercised the seam between the browser and the route. The route file was missing a handler. The regression test now pins the route's exported methods. When a screen fails but the services are all tested, suspect the route, not the logic.

What the venue has now is one system: an enquiry becomes a booking, a booking holds a date, a public event sells tickets that are checked at the door, and the person who owns the business can change what the website says without calling anyone.

21

CRM screens built

1,143

Automated tests passing

0.013s

Warm image response after cache fix

30

Help-centre topics written for staff

Deliverables

Public venue website — services, gallery, events, journal and contact
21-screen CRM covering leads, pipeline, bookings, tours and a shared calendar
Stripe Checkout ticketing with signature-verified webhook fulfilment
Counter sales for walk-up ticket purchases, reconciled against takings
Door check-in by receipt code or QR scan
AI website chatbot answering from owner-editable brand knowledge
Owner-controlled content: headline numbers, catering menu, gallery, blog, site images
Role-based staff accounts (Owner, Admin, Agent)
30-topic in-app help centre written for venue staff
Self-hosted deployment: Docker, PostgreSQL, automatic TLS, nightly encrypted backups

Technologies

Next.jsReactTypeScriptTailwind CSSNode.jsPostgreSQLPrismaDockerStripeCloudflareCoolifyCloudinaryLinux

Project Screenshots

Emerald San Ramon homepage hero — "A hall built for celebration" over a photograph of the venue dressed for a wedding, with Book Your Event and Book A Tour calls to action
Public events page listing ticketed events open to the public, with private bookings deliberately excluded
Emerald CRM dashboard showing booking and enquiry counters, pipeline stages, leads by source, and a first-run setup checklist. Customer names and messages are blurred
CRM leads table with status, guest-count band, source, assignee and last update. Client names, emails and phone numbers are blurred
The same leads as a drag-and-drop pipeline board across New, Contacted, Proposal, Won and Lost. Client names are blurred
Shared month calendar where bookings are filled, tours outlined, and an amber entry marks a visitor still waiting on a callback
Ticket events screen showing a public event with price, tickets sold against capacity, remaining inventory and on-sale status
Counter sales — the box-office screen for selling a ticket to someone standing at the desk, with card or recorded cash payment and a running total to take
Door check-in screen accepting a receipt code by typing or QR scan, with a running count of guests admitted this session
CRM settings — the plain-text brand knowledge the website AI chatbot answers from, plus owner-editable headline numbers and catering menu. Venue contact details are blurred
Blog manager with nine published posts targeting venue search terms, each showing its live URL and publish state
Gallery manager holding three albums and thirty-nine photographs published straight to the public website
Staff accounts with Owner and Admin roles. Names and email addresses are blurred
The in-app help centre — thirty topics covering every screen, written for the people who run the venue rather than for developers
Reviews screen, which records reviews customers already left elsewhere and links back to the original rather than allowing new ones to be written

Ready to build something similar?

Tell us about your project and we'll show you exactly how we'd approach it.

Start a ProjectSee More Work