System Architecture — Team Document
v1.3 · OCT 2026
Architecture philosophy: proven, widely-deployed technology, professionally operated, sized for the real market (Utah local commerce) with a pre-planned path to 100× scale. No speculative complexity: every component below exists because a requirement demands it — and every one has millions of production deployments, deep documentation, and a large hiring pool behind it.
How to read this page: the diagram shows the launch architecture, with each component marked by where it stands today. Components already running carry a solid border; components still to come carry a dashed border. The section after the diagram describes the three environments that exist right now and how code moves into them.
Client Layer
Edge Layer — Cloudflare
Application Layer — Laravel 13 (PHP 8.3) on a dedicated IONOS VPS (Ubuntu 24.04, Plesk)
Data Layer
Integration Layer
Operations Layer
Three environments, each with one job. All application traffic runs on a dedicated IONOS virtual server (6 vCore, 8 GB RAM, 240 GB NVMe, Ubuntu 24.04, managed through Plesk). Both apps on that server read and write the same database, so a deal created in one appears immediately in the other.
The permanent home is app.ribbit.deals. Moving to it is a DNS and certificate change, not a code change. The genxapp.org domains are interim.
Database changes for the production build are written as Laravel migrations, so the schema's history lives with the code and can be rolled back. Changes that the prototype needs ahead of Laravel are applied as reviewed SQL scripts. A dated backup is taken before every deploy.
Staging runs MariaDB 10.11, a drop-in, MySQL-compatible database shipped with the server's operating system. The schema, queries, and transaction behavior the architecture depends on (row locks for redemption caps, integer-cents money, append-only ledgers) are identical. Managed hosting on the scale path offers MariaDB and MySQL alike, so this choice does not affect any later step.
The original 16-table schema (v1.1) has grown by eight tables as features shipped:
| Added | Purpose |
|---|---|
categories | Admin-editable deal categories with optional sub-categories |
platform_settings · platform_settings_audit | Fee rates, the Home feature slot, and drawing settings, with a full change history |
platform_admins · admin_audit | Admin access as a dated grant, and a log of every admin action |
password_reset_tokens | Self-service password reset |
offer_views | Deal view counts for the business funnel, recorded without location |
media | The private deal-photo library (linked from each offer) |
Columns were also added for member suspension (with a recorded reason). Laravel adds its own framework tables for sessions, cache, queued jobs, and migration history.
Deal photos are stored on the server outside the public web folder, in a library separated by business. No photo has a public address: the app requests it by an unguessable ID through a gatekeeper route, which checks access before sending it. Every photo is framed to one standard shape (3:1, 1800 × 600) so the deal card always fits on screen; the shape is defined in a single setting in each app.
The hosting provider blocks direct outgoing mail from the server (a common anti-spam measure). App email is therefore relayed through the provider's authenticated mail service as no-reply@genxapp.org, which also gives it better deliverability. A dedicated transactional-email service remains an option when statements and receipts raise the volume.
| Choice | Why it survives scrutiny |
|---|---|
| Laravel 13 / PHP 8.3 | The most widely deployed web framework family on earth; batteries included (auth, queues, scheduler, migrations, Stripe library); enormous hiring pool — no key-person risk. Modern PHP 8 benchmarks within range of Node/Python for this workload. |
| MariaDB (MySQL-compatible) | Boring in the best way: the MySQL family has 25+ years in production everywhere. Our schema is already deployed and running on it — transactions, row-locks for redemption caps, and a straight upgrade path to a managed database. |
| Git + automatic deploys | Every change is recorded, reviewable, and reversible; deploys are identical every time because no step is done by hand. |
| Redis | Industry-standard cache/queue; keeps the RIBBIT moment fast and notification work off the request path. Until volume calls for it, the database carries this role with no code change needed to switch. |
| Cloudflare edge | DDoS, WAF, rate limiting, and global caching before traffic touches our server — enterprise-grade shielding at startup cost. |
| Stripe / Twilio | Card data and carrier compliance are the two scariest liabilities — we place them with the two companies best at exactly those, keeping us in the lightest PCI scope (SAQ-A: card data never touches our servers). |
| Dedicated VPS now, managed path later | Right-sized for launch; the path up (below) is planned, tested, and boring — no re-architecture required at any step. |
Honest capacity math: Utah's whole population is ~3.5M. A successful first year — tens of thousands of members, hundreds of businesses — generates request volumes a single well-tuned 4-vCPU server handles with a large margin; the current server has 6 (redemptions are short transactions; discovery is cache-friendly; static assets will not touch the server at all once the CDN is in front). We are deliberately not building Facebook's infrastructure to serve Saratoga Springs — but the road up is mapped:
| Stage | Trigger | Move | Downtime |
|---|---|---|---|
| 1. Launch | — | Hardened VPS: app + MariaDB + Redis, Cloudflare in front, on the permanent domain | — |
| 2. Vertical | Sustained CPU/RAM pressure | Resize the VPS (minutes, reversible) | ~ minutes, off-peak |
| 3. Split data | DB contention | Move the database to a managed service (AWS RDS / DO Managed, MariaDB or MySQL) — automated backups, failover | Near-zero (replica promote) |
| 4. Horizontal | App saturation | 2+ app servers behind a load balancer; sessions in Redis so this is config, not code | Zero |
| 5. Beyond Utah | Multi-state expansion | Read replicas per region · queue workers scaled independently · same codebase | Zero |
The architecture above requires zero rewrites at any stage — each step is an operational move, not an engineering project. That is the point of choosing boring, horizontally-friendly components on day one.
The prototype lets the founders click through and decide on UX quickly, against real data, while the production build is engineered properly underneath. Both share one database, so nothing is entered twice. Each screen is rebuilt in Laravel once its design is settled, and the prototype retires piece by piece; members will only ever use the production build.
We use cloud infrastructure where it earns its keep (edge shielding, managed services on the scale path, S3 backups) and skip complexity that only pays at 100× our realistic load. Microservices solve organizational scaling problems for 200-engineer teams; for a focused product they add failure modes and cost. Our monolith is modular internally and splits cleanly later if ever needed.
Two languages, the standard split: JavaScript in the client — the PWA's service worker, geolocation, camera scanning, push notifications, and all interactivity — and PHP (Laravel framework) on the server — the API, auth, redemption engine, and billing. Structured data lives in MariaDB (SQL). Nothing exotic: this is the same division of labor running most of the transactional web.
PHP 8 with Laravel runs a massive share of the transactional web. It is fast, typed, actively developed, and has one of the deepest talent pools in software — which is a continuity guarantee for a small company. The framework ships production-grade auth, queues, and billing integration we would otherwise build by hand.
It is the MySQL-compatible database that ships with the server's operating system, maintained by MySQL's original creators. Our schema and code run on either without changes, and every managed-database provider on the scale path supports both.
Cloudflare absorbs the traffic spike at the edge; the five-stage scaling path (above) is pre-planned and rewrite-free. Stage 2 is a resize measured in minutes. We would be delighted to have this problem and we know exactly which knob to turn first.
We minimize what there is to steal: no card numbers (Stripe holds those), location only at redemption moments, four fields of personal data, and deal photos that are never publicly addressable. Defense in depth — edge WAF, hardened server, least-privilege access, encrypted backups — plus an incident plan. The cheapest breach is the data you never collected.
Everything is mainstream: any Laravel shop in Utah can pick up this codebase. The full change history is in Git, documentation lives with the code (this page, the functional spec, the data layer doc), the schema uses framework conventions, and there is no exotic infrastructure requiring specialist knowledge.
At launch scale, yes, mitigated by monitoring (60-second checks), hourly offsite backups, and a documented restore that has actually been rehearsed — worst case is measured in minutes-to-hours of recovery, appropriate for this stage. Stage 3–4 of the scaling path adds managed-database failover and multi-server redundancy when the business justifies it.
Two independent factors — the member's verified location (redemption is refused without it) and a scan of the business's own counter placard — validated server-side, plus a live anti-screenshot verification screen and the business's 48-hour void window. Fraud requires being physically present at the counter with staff watching, and the business still holds the undo button.
See the decision record in the functional spec (§6): integration multiplies development, certification, maintenance, and support cost across every POS platform forever, delays launch past the season, excludes cash-only merchants — and buys little, because the placard handshake plus the void window already verifies real visits. It remains a data-driven Phase 2 option if merchants ask.
| Version | Date | Changes |
|---|---|---|
| v1.3 | Oct 7, 2026 | The original business tools are retired (they redirect to the production build’s business tools, which now include “Forgot password?”). The prototype now holds only the member app. |
| v1.2 | Oct 7, 2026 | The admin dashboard, enrollment, business tools and billing now run in the production build; the original admin redirects there. Billing engine, Stripe (test mode) and the scheduler marked live. The redemption engine remains in the prototype until the member app moves (M4). |
| v1.1 | Oct 2026 | Matched the document to the running system. Added status markers (live, next, pre-launch) to every component. Added “Where it runs today”: the three environments, the Git deploy pipeline, the database and its eight added tables, the private deal-photo library, and the email relay. Updated Laravel 11 → 13 and MySQL 8 → MariaDB 10.11 (MySQL-compatible). Added admin access grants, audit logging, fee settings, and member suspension to the application and security sections. Added Q&A on the two-app approach and the database choice. |
| v1.0 | Aug 2026 | Initial architecture: layers, stack rationale, scaling path, security posture, advisor Q&A. |