Nudge Tags — the whole machine, on one page
Every domain, every login, every key, every scheduled job, every table in the database and every process that turns a sheet of acrylic into recurring revenue. Written so either founder can pick it up cold.
01 What the business actually is
Two products, sold together, in that order. The plaque gets you in the door; the subscription is the business.
The hardware Shipping
A cast-acrylic plaque with a passive NFC chip on the back. A customer holds their phone near it and a page opens. No app, no battery, no camera, nothing to scan.
- Round
- Ø90 mm · 3 mm cast acrylic · NTAG215 or better · points at anything: menu, bookings, ordering, events
- Square
- 100 × 100 mm · 3 mm cast acrylic · NTAG213 · the Google-review plaque, five brass stars
- Finishes
- Sand, Ink (black), Chalk (white), Forest (green)
- Fixing
- 3M VHB full-base adhesive; Ø25 mm chip surface-stuck on the back, reads through the 3 mm face
- Price
- Regular $15 each, $10 at 10+. Custom $20 each, $15 at 10+. Plus a flat $50 install per job
The software Live
Nudge Reviews at app.nudgetags.com. Every plaque points at a link we own, so the destination can change without reprinting, every tap is counted, and the whole thing stops working the day they cancel.
- Price
- $149/mo per venue, or $99/mo billed yearly ($1,188). AUD.
- Rule
- Every hardware sale includes the subscription. Non-negotiable — it is the actual business.
- Per venue
- Reviews, menus, or both — same price. Team members are free.
- Sold
- In person only. Public sign-up is deliberately switched off.
Who owns it
A partnership between Gauresh Malhotra and Leon Allendes trading as Nudge Tags. 50/50. Explicitly not a Purple Pi Marketing property — no PPM name, colour or address may appear on anything Nudge-facing.
- ABN
- 64 539 870 400 (the partnership's)
- Contact
- contact@nudgetags.com · 0481 118 358
- Law
- Queensland. Prices in AUD, exclusive of GST.
- Not done
- Business name not registered with ASIC; no Pty Ltd; Stripe not activated for live payments
The moat, in one sentence
Anyone can buy an NFC sticker for two dollars. What they cannot buy is a plaque whose destination you control, whose taps you can count per table and per hour, and which switches off when the invoice does. The sticker is the cheap part; the redirect layer and the dashboard are the product.
- Repoint
- Change where a plaque sends people, from the dashboard, without touching the plaque
- Measure
- Each plaque and each table has its own code, so taps split by placement, hour and day
- Leverage
- Cancellation pauses every link the venue has, immediately
~/Documents/claude/nudge-tags/) and the
Google Drive backup of that folder. A bad overwrite, a disk failure or a wrong rm loses work with no undo,
and there is no history to see what changed between two deploys. Putting the repo on GitHub (private) is the single
highest-value hour of maintenance available.
02 The whole process, start to finish
Three tracks run in parallel: making the plaque, the tap itself, and the money. Click any box for the detail behind it.
03 Every address we own
Four things are live on the internet under this brand. Everything else is a redirect or a reserved name.
| Address | What it is | Hosted by | Source on disk | Who can see it | State |
|---|---|---|---|---|---|
| nudgetags.com www → same |
The public marketing site. 8 hand-written HTML pages with a live 3D plaque showroom. | Vercel project nudgetags | nudge-tags/website/ | Anyone | Live |
| app.nudgetags.com | The product. Marketing front page, customer tap pages, the venue dashboard, the operator console, the finance dashboard and the new in-app quote builder. | Vercel project nudge-reviews (Next.js, region syd1) | nudge-tags/nudge-reviews/ | Mixed — see the route table below | Live |
| quote.nudgetags.com | Secured 20 Aug 2026. 307-redirects on every path to app.nudgetags.com/quote, which is behind the admin login. The old public calculator no longer serves here. | Vercel project nudge-tags-quote | nudge-tags/quote/index.html | Admin login only | Secured |
| finances.nudgetags.com | Not an app — a 307 redirect to app.nudgetags.com/finances, on every path. A shortcut, nothing more. | Alias on nudge-reviews | next.config.mjs | Redirects to a login wall | Live |
| nudge-reviews.vercel.app | The old host. 307-redirects to app.nudgetags.com preserving the path, kept because a QR code printed against it may be on someone's counter. Deliberately temporary, not permanent — browsers cache permanent redirects hard, and this is the fallback if DNS on the custom domain ever breaks. | Vercel | next.config.mjs | Anyone | Kept on purpose |
| This page nudge-tags-ops.vercel.app |
The operations manual you are reading. Internal, noindexed. A subdomain (ops.nudgetags.com) is one GoDaddy CNAME away and is how Leon gets access without a Vercel account. | Vercel project nudge-tags-ops | nudge-tags/ops/ | Anyone with the link | Live |
| go.nudgetags.com | Reserved for short plaque redirect links. No DNS record exists yet. | — | — | — | Unused |
DNS and email — where the domain actually points
Registrar & nameservers
GoDaddy. The nameservers are deliberately left at GoDaddy (ns39/ns40.domaincontrol.com) rather than moved to Vercel, because the domain carries the Google Workspace mail records — moving them would have taken the email down.
- A @
- 76.76.21.21 → Vercel
- CNAME www
- nudgetags.com
- CNAME app
- cname.vercel-dns.com
- CNAME finances
- cname.vercel-dns.com
- CNAME quote
- Vercel (*.vercel-dns-017.com)
- MX
- Google Workspace (5 records, aspmx.l.google.com et al)
Google Workspace on nudgetags.com, admin account contact@nudgetags.com. In Gauresh's Chrome it is profile index 5 — go straight to admin.google.com/u/5/ rather than hunting through profiles.
- Uses
- Contact address on every page and the contract; the FormSubmit relay target; the intended Resend sender
- Logo
- Set to the full-name wordmark, 320×60
- Open
- Account avatar is still the grey letter tile — the upload needs a native file picker, so it has to be done by hand
Certificates and renewal
TLS is issued and renewed automatically by Vercel for every attached domain — nothing to diarise there. What does need a diary entry is the domain registration itself at GoDaddy; if it lapses, every surface above dies at once, including email.
- Check
- Auto-renew is on and the card on file is current
- Warning
- The marketing site sends HSTS with preload and includeSubDomains. If the domain is ever submitted to the browser preload list, every subdomain is locked to HTTPS in shipped browsers and it is very hard to reverse.
Every route on app.nudgetags.com
This is the complete list. Anything not here does not exist.
Open to the public — no login
| Route | What it does | Notes |
|---|---|---|
| / | Product marketing page with a live rating calculator and the pricing table | Prices shown here come from one constant in the code — changing it also changes the Terms page |
| /r/<slug> | The plaque link. Records the tap, then shows the five stars | No JavaScript required. Never cached. Falls through to the menu for menu-only venues |
| /r/<slug>/rate | Receives the star tap and routes to Google or the private form | Rate-limited: 6 per visitor per 5 min, 400 per venue per 5 min |
| /r/<slug>/feedback | Saves the private complaint and emails the owner | Saved before the email is attempted, so a mail failure never loses it |
| /m/<slug> | A menu tap — records it and forwards straight to the menu | Built but orphaned — nothing links to it. See the risk list |
| /w/<key> | The embeddable ratings badge a venue puts on its own website | An iframe, deliberately not a script. Framing by anyone is intentional |
| /login /forgot /reset/<token> | Sign in and password recovery | Reset emails cannot currently be sent — see the keys register |
| /signup | Shows "accounts are set up with your plaques" | Public sign-up is closed unless SIGNUP_OPEN=1 |
| /join/<token> | A staff member accepts a team invite | Invite valid 14 days, single use, revocable |
| /terms /privacy /refund | The product's legal pages | Real commitments — 30-day yearly refund, 90-day data retention |
| /api/stripe/webhook | Where Stripe reports payments | Rejects everything without a valid signature |
| /api/error | Where the browser reports a crash | Unauthenticated by design, 20 per IP per 5 min |
| /api/cron/places /api/cron/report | The two scheduled jobs | Guarded by CRON_SECRET |
Signed in — a venue owner or their staff
| Route | What the customer does here | Who |
|---|---|---|
| /onboarding | First screen after setting a password: name the venue, paste the Google link, pick the threshold | Owner |
| /dashboard | Overview — six tiles, the Google-rating line, activity chart, star spread, reviews-vs-menus split, per-venue table, per-placement table | Everyone |
| /dashboard/locations | The reviews tab: the link, the QR, a phone preview, six charts, the star funnel, and the placements list where each plaque gets its own code | Everyone reads · owner edits |
| /dashboard/menus | The menu tab: the menu link and one code per table, added singly or as a range | Everyone reads · owner edits |
| /dashboard/staff | Staff leaderboard — each person gets their own code, ranked by ratings earned rather than taps | Everyone reads · owner edits |
| /dashboard/feedback | Every private complaint, themed automatically, with mark-resolved and a CSV export | Everyone; members may resolve |
| /dashboard/settings | Venue switches, Maps link, timezone, the ratings badge, own profile and password, team invites | Everyone; venue and team edits are owner-only |
| /dashboard/billing | Plan, checkout and the Stripe portal | Owner only |
| /dashboard/setup/<id> | Bolt a missing capability onto an existing venue | Owner only |
| /api/google-link | Converts a pasted Maps link into a direct review link, live, before saving | Signed in — deliberately, since it follows redirects server-side |
| /dashboard/locations/<id>/qr /dashboard/menus/<id>/qr | Downloads the QR for one placement — 1024 px SVG, ink on white, so it prints crisply even at receipt size. Generated by our own encoder, so no third party ever sees a customer's link | Signed in, scoped to venues they may see |
| /dashboard/feedback/export | CSV of the private feedback inbox, up to 5,000 rows | Signed in |
| /logout | Signs out | Anyone |
Ours only — the operator surfaces
| Route | What it is | Gate |
|---|---|---|
| /admin | The operator console. Create a customer account and get their 7-day setup link; see every business, its owner email, plan status, venue count and rating count; see the last 25 crashes and the 24-hour error count | Email listed in SUPERADMIN_EMAILS |
| /finances | The founder finance dashboard. Overview, Revenue, Expenses, Units & Inventory, Ledgers — live from the accounting sheet | Same |
| /quote | The in-app quote builder (new). Prices an order against real stock and real landed cost, then logs the closed sale straight into the Income tab of the accounting sheet | Same |
| /api/finances | The JSON behind the finance dashboard | Same — returns 403 otherwise |
| /api/quote/close | Writes a closed sale to the sheet. The browser never sees the sheet's write token | Same |
Right now it holds only Gauresh's own addresses (the gmail and contact@nudgetags.com — worth re-reading the live value in Vercel before assuming). Leon cannot open /admin, /finances or /quote. There is no way to grant the finance dashboard without also granting the operator console over every customer account — if that split matters, it needs building.
04 Every URL, complete
Every address the business owns or answers on — public pages, customer links, operator-only screens, machine endpoints, redirects and consoles. If a URL exists and is not on this list, the list is wrong.
nudgetags.com — the public marketing site
| URL | What it is | Access |
|---|---|---|
| nudgetags.com | Home — the pitch in one scroll | Public |
| /plaques | The 3D showroom and the full physical spec | Public |
| /plaques?p=circle|square&c=sand|black|white|green | Deep link to one finish. Eight combinations — paste the exact one you are describing on a call | Public |
| /how-it-works | The mechanics, the redirect moat, and the three honest limits | Public |
| /software | Sells the subscription half | Public |
| /faq | Objections answered, in four groups | Public |
| /contact | Enquiry form · anchor #enquiry-form. Serves both a walkthrough booking and a custom-plaque quote | Public |
| /enquiry-received | The confirmation page. The form navigates here on success, so a completed enquiry is a real page view with a URL a pixel can fire on. Noindexed | Public |
| /terms · /privacy | Website terms and the privacy notice | Public, noindexed |
| www.nudgetags.com | Redirects to the bare domain | Public |
| /product · /products | → /plaques (permanent) | Redirect |
| /pricing | → /contact. Plaque prices are never published; software prices are, on the app's own pricing page | Redirect |
| /reviews | → /software (temporary) | Redirect |
app.nudgetags.com — customer-facing
| URL | What it is | Access |
|---|---|---|
| app.nudgetags.com | The product's own marketing and pricing page | Public |
| /r/<slug> | The plaque link. This is what goes on a chip | Public |
| /r/<slug>?c=<code> | One specific placement — a plaque, a table, a staff member. The ?c= code is the entire tracking mechanism | Public |
| /r/<slug>/rate · /feedback | The star tap and the private complaint form (both POST) | Public |
| /api/enquiry | Where the website contact form posts. POST + OPTIONS, CORS limited to nudgetags.com. Writes to enquiries and emails a notification | Public endpoint |
| /demo/menu | A sample menu for a fictional venue. Demo 2's table tags open this, so a demo never sends a prospect to a real unrelated business. Noindexed | Public, unlisted |
| /m/<slug> | Menu tap — records it and forwards to the menu e.g. app.nudgetags.com/m/six-seven live | Public |
| /w/<key> | The embeddable ratings badge a venue pastes on its own site. Public by design, framed by anyone e.g. app.nudgetags.com/w/e785ed17498a4f7a86b6 live — the demo venue's real badge | Public, unguessable key |
| /welcome/<business-slug> | Generated when a close is logged with the dashboard included (the default). Shows their order and their review-page link e.g. app.nudgetags.com/welcome/the-lark-cafe — illustrative; no close has been logged yet | Public, unlisted |
| /thanks/<business-slug> | Generated on a hardware-only close. Order summary only — no workspace, no review link e.g. app.nudgetags.com/thanks/the-lark-cafe — illustrative | Public, unlisted |
| /login · /forgot · /reset/<token> · /logout | Sign in and password recovery. The reset link doubles as the 7-day setup link a new customer receives e.g. app.nudgetags.com/reset/8a41f0c92b7e5d63a10f4c8b2e97d5061a3f8c24 — 40 hex characters, single use | Public |
| /signup | Says accounts come with the plaques — self-signup is switched off | Public |
| /join/<token> | A staff member accepts a team invite. 14 days, single use e.g. app.nudgetags.com/join/3d7b1e5a9c04f28e6b7152ac8de60934f1b7a2c8 — shown once, never recoverable | Public, unguessable |
| /terms · /privacy · /refund | The product's legal pages — real commitments | Public |
Not every submission ends at a page, and the ones that don't still need writing down or nobody can review what the customer was told. Three of ours confirm in place, with no navigation and therefore no URL to visit:
| Form | What the customer sees on success |
|---|---|
| nudgetags.com/contact | Now navigates to /enquiry-received. The inline line is only used on failure: “That did not send. Ring 0481 118 358 or email contact@nudgetags.com and we will pick it up straight away.” |
| /r/<slug>/rate | Either a redirect to Google, or the feedback form rendered in place. A staff card always renders the feedback form, whatever the stars. |
| /r/<slug>/feedback | A thank-you rendered in place at the same URL — there is no /thank-you route for it. |
The contact form now has one — /enquiry-received, added 22 Aug 2026 for exactly this reason. The two review-flow forms still confirm in place, which is correct: they are customer moments, not conversions we would ever advertise against. GA4 is still not installed anywhere, so the confirmation page currently has nothing listening to it.
The two generated pages are worth understanding: the slug is the customer's name lowercased with anything non-alphanumeric turned into hyphens, capped at 48 characters — "The Lark Café" becomes /welcome/the-lark-caf. Each close's exact URL is also written onto its row in the Income sheet, so the link is recoverable from the books.
app.nudgetags.com — signed in
| URL | What it is | Access |
|---|---|---|
| /onboarding | First screen after setting a password | Owner |
| /dashboard | Overview | Owner & team |
| /dashboard?loc=<id> | Scoped to one venue — the workspace switcher writes this e.g. app.nudgetags.com/dashboard?loc=529a176891554522b8e4 — the demo venue's real id | Owner & team |
| /dashboard/locations · /menus · /staff · /feedback · /settings | The five working tabs | Owner & team |
| /dashboard/billing | Plan, checkout, Stripe portal | Owner only |
| /dashboard/setup/<id>?add=reviews|menu | Bolt a missing capability onto a venue e.g. app.nudgetags.com/dashboard/setup/529a176891554522b8e4?add=menu | Owner only |
| /dashboard/locations/<id>/qr?c=<code> /dashboard/menus/<id>/qr?c=<code> | Downloads that placement's QR as a 1024 px SVG e.g. …/locations/529a176891554522b8e4/qr?c=plaque-1 e.g. …/menus/7db6a67786a345359a99/qr?c=table-1 — both real ids | Owner & team |
| /dashboard/feedback/export?loc=<id> | Private feedback as CSV e.g. …/feedback/export?loc=529a176891554522b8e4 | Owner & team |
app.nudgetags.com — ours only
| URL | What it is |
|---|---|
| /admin | Operator console. Create customer accounts, see every business, read the last 25 crashes |
| /finances | The real numbers, live from the accounting sheet |
| /finances?tab=overview|revenue|expenses|units-inventory|ledgers&range=all|12m|6m|3m|1m&q=… | Deep link to one tab, range and search — survives a refresh and can be sent to the other founder |
| /quote | The quote builder. Prices from the price list against real stock, logs the close to the books, generates the welcome page |
Machine endpoints — nothing to click, but they exist
| URL | Called by | Guarded by |
|---|---|---|
| /api/stripe/webhook | Stripe, on every payment event | Signature check |
| /api/cron/places | Vercel Cron, 05:00 Brisbane daily | CRON_SECRET |
| /api/cron/report | Vercel Cron, 07:30 Brisbane on the 2nd | CRON_SECRET |
| /api/error | The browser, when a page crashes | Unauthenticated, rate limited |
| /api/google-link | The dashboard, converting a pasted Maps link | Signed-in session |
| /api/finances | The finance dashboard, every 60 seconds | Superadmin |
| /api/quote/close | The quote builder, logging a sale | Superadmin |
Subdomains and redirects
| URL | Goes to | State |
|---|---|---|
| quote.nudgetags.com | 307 → app.nudgetags.com/quote on every path, landing on the admin login. The retired nudge-tags-quote project now serves only a no-pricing stub that redirects the same way, so neither URL exposes cost or margin | Secured |
| finances.nudgetags.com | 307 → app.nudgetags.com/finances, on every path | Live |
| nudge-reviews.vercel.app | 307 → app.nudgetags.com, path preserved. Kept because a QR printed against it may be on a counter | Live |
| nudge-tags-ops.vercel.app | This page. A subdomain (ops.nudgetags.com) is one CNAME away | Live |
| go.nudgetags.com | Reserved for short plaque links. No DNS record yet | Not created |
| ops.nudgetags.com | Intended home of this manual | Not created |
Updated 22 Aug 2026: no .vercel.app address serves anything any more. ops.nudgetags.com was created and the Operations Map moved onto it; nudge-tags-ops and nudgetags.vercel.app now 307 to the owned domain, joining nudge-reviews.vercel.app which already did. quote.nudgetags.com was moved off the retired quote project and its redirect now fires. Per-deployment URLs (<project>-<hash>-purple-pi-marketing.vercel.app) still resolve, embed the team name, and should never be sent to anyone.
Consoles — where to log in
| What | URL |
|---|---|
| Vercel — all four projects | vercel.com/purple-pi-marketing |
| Supabase — the database | supabase.com/dashboard/project/nlpqsgqrixerlsqhmkms |
| Stripe — sandbox only today | dashboard.stripe.com |
| Google Workspace admin | admin.google.com/u/5/ |
| Google Apps Script — the finance feed | script.google.com |
| GoDaddy — domain and DNS | dcc.godaddy.com/control/dnsmanagement?domainName=nudgetags.com |
| Google Cloud — for the Places key | console.cloud.google.com (no project created yet) |
| Resend — email | no account yet |
| The accounting sheet | Drive › Nudge Tags › Accounting |
| The enquiry-form relay (retired 22 Aug 2026 — replaced by our own /api/enquiry) | formsubmit.co/ajax/contact@nudgetags.com |
05 Google — what it does, when it costs money, and what to check
There is no single "Google API". There are five separate touchpoints, and only three of them cost anything. The one that earns the money is free.
The five touchpoints
| # | What | Used for | Costs money? | Working today? |
|---|---|---|---|---|
| 1 | The write-a-review deep link no API at all |
The money mechanic. Sends a happy customer into Google's "write a review" box with it already open, instead of the listing page where nobody finds the button. | Free — it is just a URL | Yes |
| 2 | Places API (New) — Text Search places:searchText |
Looks a venue up once to store its address, coordinates and photo reference, so the dashboard card shows where the place is. | Yes, per lookup | No key |
| 3 | Places API (New) — Place Details places/<id> |
"What is this venue's public star rating today?" Runs once per venue per day and builds the rating-over-time line — the chart that proves the plaques worked. | Yes, and this one is the expensive tier | No key |
| 4 | Street View Static (with Places Photo as fallback) | The shopfront thumbnail on the venue cards. Street View first because it is an actual photo of the building; user-uploaded Places photos are more often a plate of food. | Yes, per image | No key |
| 5 | Google Apps Script over a Google Sheet | The accounting feed — how the finance dashboard reads the books and how the quote builder writes a closed sale back. | Free (Workspace quota) | Yes |
How the free part works — the bit that actually earns
Every Google Maps URL for a place carries a feature id that looks like !1s0x6b91598d0ef1ec8f:0x5177853dd7667eae. Dropped into a specific URL shape with the action code !12e1, that opens the review composer directly:
https://www.google.com/maps/place//data=!4m3!3m2!1s<FEATURE-ID>!12e1
↑
the empty name segment is required — do not "tidy" it away
When an owner pastes a link, the app runs this ladder:
- Is it Google at all?
The hostname must be one of g.page, goo.gl, maps.app.goo.gl, google.com, search.google.com, maps.google.com, business.google.com. Anything else is refused with "That link isn't a Google one."
- Is it already a review link?
If it contains /writereview, /review or !12e1, it is left exactly as it is. A g.page/r/…/review link from Google's own "ask for reviews" button is already the best form there is.
- Is it a short link?
A maps.app.goo.gl link — what the Maps share button gives you — is followed server-side to the full URL. Hard limits: two hops, five seconds, and each hop must still be a Google host. This is why the conversion endpoint requires a login: otherwise it would be an open URL fetcher for anyone.
- Pull out the feature id and rebuild.
The owner is shown exactly what changed: "We converted your Maps link so it opens the review box straight away." Nothing is ever silently rewritten.
- A ?cid= link is saved but flagged.
A CID is only half the feature id, so the review link cannot be built from it. Rather than quietly save something that lands on the listing, the owner is told to re-copy the URL from the browser bar while viewing the place.
!12e1 format is undocumented. Google can change it without notice. If they do, every plaque already glued to a counter quietly starts landing on the listing page instead of the review box — no error, no alert, just a collapse in conversion that nobody would spot for weeks. Nothing in the system monitors this. Once a quarter: tap a real plaque, give five stars, confirm the review box actually opens.
Turning the paid part on — the exact steps
Everything above the line works with no key. Below the line is what a key buys, and it is one key for all four paid calls: GOOGLE_PLACES_KEY. It is not set in production today, which is why every venue card shows a lettered tile instead of a photo and the rating-over-time chart is permanently empty.
- Create a Google Cloud project
Under the nudgetags.com Workspace account, not a personal one, so it survives a laptop change. Attach a billing account.
- Enable two APIs — they are separate switches
Places API (New) and Street View Static API. Enabling one does not enable the other, and the failure mode is a silent null rather than an error.
- Set a budget alert and a quota cap before anything else
Google removed the old flat $200/month credit in 2025 in favour of per-SKU free allowances. Nothing in our code caps spend. Set a hard daily quota per API and a billing alert at a figure you are willing to lose.
- Mint two keys, not one
This matters. The shopfront photo is loaded by the customer's browser, which means the key ends up in the page source where any signed-in customer can read it. A server key must be IP-restricted or secret; a browser key must be referrer-restricted — and Google does not let one key carry both kinds of restriction.
Key A (server): Places API (New) only, used by the lookup and the daily rating job. Key B (browser): Street View Static + Places Photo only, restricted by HTTP referrer to app.nudgetags.com. Splitting these is a small code change and it is the difference between a leaked key being harmless and a leaked key being someone else's bill.
- Set
CRON_SECRETin the same deployThe daily rating job spends money per venue. The code fails closed — the moment a Places key exists without a cron secret, the job returns 503 rather than let an open endpoint run up a bill. Set both together or the ratings never start collecting.
- Add the key in Vercel and redeploy
Project nudge-reviews → Settings → Environment Variables → Production. Environment changes need a redeploy to take effect.
- Backfill the venues
The address lookup only fires when someone changes the Maps link in Settings — deliberately, so unrelated saves don't cost money. Existing venues will need their Maps link re-saved once each to pick up an address and photo.
"How is it enabled per venue?"
It isn't, and this is the single most misunderstood part of the setup. The key is global — one key, all customers. What varies per venue is whether that venue has given us something to look up. There are two different Google fields on two different screens, and filling one does not fill the other:
"Google review link" Free
Entered during onboarding, on the Reviews tab, or in the setup wizard. Stored in locations.google_url.
- Job
- Where a happy customer is sent. This is the revenue path.
- Without it
- The venue cannot collect reviews at all — the tab locks
- Cost
- Nothing, ever
"Google Maps link" Billed once
Entered in Settings only. Stored in locations.maps_url, then resolved to place_id, address, coordinates and a photo reference.
- Job
- Address and shopfront photo on the card, and it is what makes the venue eligible for the daily rating snapshot
- Without it
- Everything still works; the card is plainer and there is no rating history
- Cost
- One lookup when saved, then one cheap call a day forever
Only two link shapes are accepted for the Maps field: the address-bar URL of a venue's page (which always contains /maps/place/) and the short link the share button produces. A search URL is refused on purpose — a search can resolve to a different business with the same name, and the wrong shopfront on a customer's card is worse than no photo.
What a human has to keep an eye on
| Item | How often | Why |
|---|---|---|
| Tap a real plaque and confirm the review box opens | Quarterly | The deep-link format is undocumented and can change silently |
| Google Cloud billing and quota | Monthly | The daily job is one billed call per venue per day — the bill scales with the customer count, and the free allowance changes |
| Key restrictions still correct | On any deploy that touches images | A referrer-restricted key breaks server calls and vice versa |
| Rotate GOOGLE_PLACES_KEY | On any suspected leak | Until the key is split in two, the browser key is visible to every signed-in customer |
| Cached addresses and photos | Yearly, or when a card looks wrong | Nothing re-syncs them. A venue that moves keeps its old address forever, and photo references are not guaranteed stable |
| Apps Script deployment URL | Whenever the script is edited | Re-deploying can change the /exec URL, which silently breaks the finance dashboard until the Vercel variable is updated |
06 Every account and service in the stack
Ten external services. For each: what it does, where to log in, what it costs, and what happens if it disappears.
Vercel Live
Hosts all three websites. Builds them, serves them, issues the TLS certificates, and runs the two scheduled jobs.
- Console
- vercel.com → team purple-pi-marketing
- Projects
- nudge-reviews · nudgetags · nudge-tags-quote
- Region
- Functions pinned to syd1 — same continent as the database. This is not optional: when they were in Washington every click took 2–4 seconds and the product read as broken
- Cost
- Currently on a Hobby-tier footprint. The monthly-report job declares a 300-second budget, which exceeds the Hobby 60-second cap — confirm the plan before switching email on
- If it dies
- Every surface goes down. Plaques stop working
- Note
- The team is named after Gauresh's other business purely because a second Vercel team requires a payment method. Nothing customer-visible carries that name; the custom domains remove the last trace
Supabase (Postgres) Live
The database. Every account, venue, tap, rating and complaint lives here. Nothing else stores customer data.
- Console
- supabase.com → project nlpqsgqrixerlsqhmkms
- Region
- ap-southeast-2 (Sydney)
- Connection
- Through the transaction pooler on port 6543, with prepared statements disabled. Both are mandatory for serverless and must not be "cleaned up"
- Plan
- Pro — so no project-pausing risk
- If it dies
- The whole product stops, including every plaque. This is the single point of failure
- Check
- Whether the 13 tables show as "Unrestricted" in the table editor. Supabase exposes public tables over its own API by default, and the app does not use that path at all — see the risk list
Google Workspace Live
The nudgetags.com mailbox — contact@nudgetags.com. Also the identity that owns the Drive folder, the accounting sheet and the Apps Script.
- Console
- admin.google.com/u/5/ (Chrome profile index 5)
- Uses
- Contact address everywhere, contract, the enquiry-form target, the intended email sender
- Cost
- Per-seat monthly
- If it dies
- Enquiries stop arriving and the contract's contact address bounces
Google Cloud (Places) Not set up
Would provide venue addresses, shopfront photos and the daily public-rating snapshot. Nothing depends on it to earn money.
- Console
- console.cloud.google.com — no project created yet
- Key
- GOOGLE_PLACES_KEY, not set
- Cost
- Per call, billed at the tier of the most expensive field requested. The daily job uses the expensive tier by necessity
- See
- The Google section for the full setup and the two-key rule
Stripe Sandbox only
Would take the subscription payments. Built, wired, and inert — every price and webhook exists in test mode only.
- Account
- acct_1U3sp5IiFcUCzjal — Nudge Tags' own, not shared
- Test objects
- Product prod_V40vszmxLJBgwi; $149/mo and $1,188/yr prices; a webhook pointed at /api/stripe/webhook with six events
- Blocked by
- Live mode needs business verification and bank details. Until that is done, no money can be collected through the product — every invoice is manual
- Before the first real charge
- Stripe Tax must be configured with an AU registration; the code requests automatic tax and checkout will hard-fail without it
- Also
- Everything created in test mode has to be recreated in live mode, and the four Stripe variables set in Vercel
Resend (email) Not set up
Would send all four of the product's emails. Without it every send is logged to the console and skipped — nothing crashes, nothing arrives.
- Key
- RESEND_API_KEY, not set
- What is dark
- Welcome email · password resets · private-feedback alerts · the monthly report
- Worst consequence
- A venue owner locked out of their account cannot recover it themselves — it becomes a manual job every single time
- Second worst
- A complaint from an unhappy customer sits unread unless the owner happens to log in
- Setup needs
- A verified sending domain for nudgetags.com — SPF and DKIM records added at GoDaddy, then kept in place forever
Google Apps Script Live
A 30-line script that reads the accounting sheet and hands it back as data, and now also accepts a closed sale and writes it into the Income tab. It is the bridge between the books and the finance dashboard.
- Where
- script.google.com, on gaureshbusiness@gmail.com, project name literally "Untitled project"
- Deployed as
- Web app, execute as me, access "anyone" — the shared token is the only guard
- Rotate
- The token lives in two places that must change together: Script Properties → FEED_TOKEN, and Vercel → FINANCE_FEED_TOKEN
- Trap
- Editing the script does nothing until you Deploy → Manage deployments → New version. Adding a new tab to the sheet also requires editing the tab list in the script and redeploying
- Trap
- The reference copy in the repo has drifted from what is deployed — it has no write handler and does not list the Inventory tab. Do not rebuild from it without re-reading the live script
GoDaddy Live
Domain registrar and DNS for nudgetags.com. Every subdomain and the Workspace mail records are edited here.
- Watch
- Auto-renew and the card on file. A lapsed domain takes the site, the app, the plaques and the email at the same moment
- Rule
- Nameservers stay at GoDaddy. Moving them to Vercel would take the mail down
FormSubmit Unverified
A free relay that turns the marketing site's enquiry form into an email. No account, no key — which is also the problem.
- Endpoint
- formsubmit.co/ajax/contact@nudgetags.com, set on the last two lines of contact.html
- Human step
- The first submission triggers a one-time activation link sent to that mailbox. Until somebody clicks it nothing is delivered
- The real danger
- An un-activated or throttled form still returns a success status, so the visitor sees "Got it" while the enquiry evaporates. Test it end to end and confirm a real email lands
- Replacing it
- One line at the bottom of contact.html. Our own forms platform or a Resend-backed endpoint would remove the dependency entirely
Alibaba suppliers Live
Where the plaques are actually made. Two factories used so far, four orders, 505 units.
- Leadercolor
- Shenzhen Leadercolor Smart Tech — orders 1 & 2, 200 plain review cards
- Huasentong
- Shenzhen Huasentong Intelligent Technology — orders 3 & 4, custom rounded squares in black/white/sand and 5 green
- Receipts
- Drive › Nudge Tags › Accounting › Expenses, INVOICE ALIBABA 1–4.pdf
- Watch
- Shipping is 29% of everything spent. Freight is the lever, not unit price
Google Drive Live
Brand assets, print masters, spec sheets, the contract, the accounting sheet, the outreach tracker and this manual. Also the only backup of the source code.
- Path
- My Drive › BUSINESS › Nudge Tags
- Rule
- Drive is the source of truth for artwork, never the local copies — a stale local duplicate once shipped an old plaque design to the live website for hours
- Access
- Owned by gaureshbusiness@gmail.com. Leon's access should be checked folder by folder
Version control Does not exist
There is no git repository for any part of Nudge Tags. No history, no branches, no way to see what changed between two deploys, no way to revert.
- Only copies
- This Mac, and the Drive backup of the same folder
- Consequence
- A bad overwrite is unrecoverable. Two people cannot work on it at once
- Fix
- A private GitHub repo. An hour's work, and it is the highest-value maintenance item on this page
07 Keys and settings — the complete register
Every setting the app reads, whether it is set today, and exactly what changes if it is wrong. Set in Vercel → project nudge-reviews → Settings → Environment Variables → Production. Changes need a redeploy to take effect.
| Setting | In plain English | If it is missing or wrong | State |
|---|---|---|---|
| DATABASE_URL | The connection to the Supabase database. Must be the pooler on port 6543. | Everything stops. In production the app deliberately refuses to start rather than silently fall back to the throwaway local database, because that would look fine while quietly losing every rating. | Set |
| AUTH_SECRET | Signs the login cookie, and salts the one-way visitor hash. | Missing: nobody can log in at all, though the marketing site and the plaque pages keep working — that exact symptom is the tell. Rotating it signs every user out everywhere (this is the only global sign-out we have) and re-buckets unique-visitor counts for that day, so rotate late at night. | Set |
| APP_URL | The real public address. Every QR code and every plaque link is built from it. | Wrong value = printed plaques pointing at a dead address, and the NFC chips are locked after writing, so the fix is physically replacing tags on counters. The single most expensive setting to get wrong. | Set |
| SUPERADMIN_EMAILS | Comma-separated list of email addresses that get the operator console, the finance dashboard, the quote builder and the power to delete a venue. | Empty or mistyped: no new customer accounts can be created and the operator link simply stops appearing, with no warning. Containing an address you don't control hands over every customer's details and the real revenue figures. Treat as a credential. | Set |
| SUPPORT_EMAIL | The contact address printed on the Terms, Privacy and Refund pages. | Falls back to contact@nudgetags.com. | Set |
| CRON_SECRET | The password Vercel sends when it fires a scheduled job, so nobody else can trigger them. | While both jobs are switched off it changes nothing. The moment a Google key or an email key exists, an unset secret makes the job return 503 and stop running entirely — a deliberate fail-closed so an open endpoint can never spend money. | Set |
| FINANCE_FEED_URL FINANCE_FEED_TOKEN |
The Apps Script address the finance dashboard reads the books from, and the shared password on it. | Either missing: the dashboard shows "Feed not configured". Mismatched token: "Feed refused". The URL plus the token together are read access to the entire company books, and the token travels in the query string, so it lands in logs. Rotate both halves together. | Set |
| STRIPE_PRICE_MONTHLY STRIPE_PRICE_YEARLY |
The IDs of the $149/month and $1,188/year recurring prices in Stripe. | Set, but useless without the secret key. They currently point at test-mode prices and will all need replacing with live-mode IDs. | Set (test) |
| STRIPE_SECRET_KEY | The key that lets the app talk to Stripe at all. | The billing page shows "Card payments aren't switched on in this deployment yet" and two price cards with no buttons. No customer can pay through the product. Nothing else breaks — accounts stay fully live. | Missing |
| STRIPE_WEBHOOK_SECRET | Proves an incoming payment notification really came from Stripe. | Every webhook is rejected, which means plan status can never change automatically — no account can ever become "active", and a cancellation would never take effect. | Missing |
| RESEND_API_KEY | Lets the app send email. | All four emails silently do not arrive: welcome, password reset, private-feedback alerts, monthly report. Nothing crashes; the UI is honest about it in three places. The practical cost is that every locked-out customer is a manual job. | Missing |
| MAIL_FROM | The From address on those emails. | Falls back to Nudge Tags <contact@nudgetags.com>, which is correct — but that domain must be verified in Resend before anything will send. | Default |
| GOOGLE_PLACES_KEY | One key for all four paid Google Maps calls. | No venue addresses, no shopfront photos, and the "your Google rating over time" chart stays permanently empty — which is the chart that proves the product worked. Nothing that earns revenue is affected. | Missing |
| SIGNUP_OPEN | Set to 1 to reopen public self-service sign-up. | Unset means closed, which is intended — the product is sold in person and an account should exist because someone bought it. Leaving it open once handed a fully working venue to anyone who guessed the URL. | Unset on purpose |
| DATABASE_POOL_MAX | How many database connections each server instance may hold. Defaults to 3. | Nothing — leave it alone unless connections are being exhausted. | Unset on purpose |
08 What we store, and where
Twenty-one tables in one Postgres database in Sydney. This is every piece of data the business holds about a customer or their customers.
| Table | One row is… | Written when | Worth knowing |
|---|---|---|---|
| businesses | One paying account — a café owner, a pub group | You create it in /admin | Holds the plan status, the venue limit and the Stripe ids. billing_exempt marks our own accounts as never-charged |
| users | A person who can log in | Account creation, or a staff member accepting an invite | Email is unique globally — one address can only ever belong to one account, forever. Passwords are PBKDF2 at 210,000 iterations |
| locations | A venue. The most important table | Onboarding, or adding a venue | Carries the public slug, the Google link, the menu link, the star threshold, the timezone, the cached Google address, and the on/off switches. 24 columns are editable and the update is restricted to an explicit allow-list so a crafted form can't write anything else |
| tags | One physical placement — a plaque, a table, a staff card, a receipt footer | Adding a placement, a table range, or a staff member | Its code is what appears in the link as ?c=. A staff card is a tag with kind "staff". Its code is random and carries no name, because it is written to the chip and has to outlive whoever holds it — the person lives in staff_members |
| visits | One page open | Every tap | The fastest-growing table. Stores the day in the venue's own timezone, the device type, and a salted hash of IP + browser + date. The IP is never stored and the hash resets nightly |
| ratings | One star tap | Every rating | Records the stars, which placement they came from, and where they went: google, private, or staff — a staff rating rates the person and never reaches Google |
| feedback | A private complaint | A gated customer submits the form | The optional contact field is the only personally identifying data in the system. The CSV export defuses spreadsheet formula injection, because this text comes from strangers |
| place_snapshots | A venue's public Google rating on one day | The daily job, once a key exists | Stored as daily snapshots rather than one current value, so the dashboard can draw the line that sells the product |
| invites | An unused team-invite link | Owner generates one | Only the hash is stored, so a stolen database can't be replayed as working links. 14-day expiry, single use, revocable instantly |
| user_locations | "This member may see this venue" | Scoped invite, or editing access | See the risk list — the edit form has a bug here |
| password_resets | An outstanding reset or setup link | Forgot-password, or creating an account | Hashed, single use, race-safe |
| rate_limits | A flood counter | Constantly | Kept in the database rather than memory because serverless instances don't share memory |
| error_log | A crash | Any uncaught error, server or browser | What the operator console shows. Contains customer email addresses — internal eyes only |
What happens when you delete something
Deleting a business erases that customer completely: their users, venues, tags, every tap, every rating, every complaint and every pending invite. Deleting a venue erases all of that venue's history. There is no soft delete, no archive and no undo. This is why deleting a venue is the most locked-down action in the whole product — it needs a superadmin email, the owner role, typing the venue's exact name, and re-entering the account password.
The intended way to switch something off is the active flag, not deletion. Pausing keeps the printed QR codes and plaques valid so they work again the moment you switch back on.
Changing the database
- Nothing runs automatically.
Not on deploy, not on start-up. A release that needs a new column will crash in production until a human runs the migration by hand. This is the most important operational fact in this section.
- The command
cd ~/Documents/claude/nudge-tags/nudge-reviews DATABASE_URL="<the pooler string>" npm run db:migrate
- There is no ledger of what has run.
Every migration file is re-executed every time. It is safe only because each one is written to be repeatable — but see the risk list, one of them is not, and it quietly re-grants access on every run.
09 Every form, and where it ends up
One rule: nothing is captured that isn't stored somewhere we own and shown on a screen someone opens. If a row can't be pointed at on this page, it shouldn't be collected. Audited end to end on 22 August 2026.
Customer-facing — the things a member of the public touches
| Capture point | Triggered by | Stored in | Shown on |
|---|---|---|---|
| /r/<slug>?c=<code> | Any plaque, table or staff card tap | visits | Overview, Google Reviews, Menus, Staff — every tap chart |
| /r/<slug>/rate | The star tap | ratings | Star funnel, placement board, staff board, monthly report |
| /r/<slug>/feedback | A private message after a rating | feedback | Feedback tab, CSV export, owner email alert |
| /m/<slug> | A menu table tap | visits | Menus tab |
| /api/tap-location | Staff card tap, integrity on, customer agrees | tap_locations | Consumed into an integrity flag, then deleted on a retention timer |
| /api/enquiry | The contact form on nudgetags.com | enquiries | Operator dashboard → Website enquiries, plus an email |
| /w/<key> | The public review widget on a venue's site | nothing | Nothing — see the gap below |
Dashboard — the things a customer or an operator submits
| Capture point | Stored in | Shown on |
|---|---|---|
| Add venue / edit venue / setup | locations | Locations, Settings, Setup |
| Add placement, table range, staff card | tags | Locations, Menus, Staff |
| Add or reassign a staff member | staff_members, staff_assignments | Staff board and Past team members |
| Set the Google review link | locations, review_link_log | Locations, with the paste-vs-converted history |
| Roster and integrity review | rosters, integrity_flags | Staff tab integrity panel |
| Generate a report | reports | Reports tab, and the PDF at /dashboard/reports/<id> |
| Team invite / accept | invites, users, user_locations | Settings → Team |
| Signup, login, password reset | users, password_resets | Operator dashboard → All accounts |
| Client-side crash | error_log | Operator dashboard → Recent errors |
| Stripe webhook | businesses | Billing, Operator dashboard, /finances |
| Quote builder → log a close | Google Sheet via Apps Script | /finances, and the accounting sheet |
Why a new link is covered automatically
New tables, staff cards and placements need no wiring. A tap records the ?c= code onto the visit, and every board reads whatever tags that venue currently has and joins on that code. Nothing enumerates placements in code, so a placement created a minute ago appears on the next page load with no deploy and no configuration.
The one thing that escaped this was traffic on a link with no matching placement — a bare /r/<slug> records as direct, and a QR printed before a tag existed keeps its old code. Those taps counted in the venue's totals but appeared in no row, so the total quietly exceeded the sum of its parts. The placement board now ends with a “Not on a placement” line, so the two always reconcile on screen.
Known gaps
| Gap | Consequence | Status |
|---|---|---|
| Widget views at /w/<key> are not recorded | A venue can embed our widget and show it thousands of times with nothing counted — no reach figure, and no way to prove the widget is worth keeping | Open |
| The enquiry endpoint accepts requests with no Origin header | A script can post junk leads. Rate limit is 5 per 10 minutes per IP and the honeypot catches naive bots; origin checks are not a real control against a non-browser client | Accepted |
Fixed in this audit
- The website contact form posted to formsubmit.co, a third-party relay that emailed the inbox and stored nothing. No count, no list, no way to tell a quiet week from a broken form — and if the relay was never activated, early enquiries were lost silently. It now posts to /api/enquiry: stored first, emailed second, listed on the operator dashboard.
- Customer GPS fixes were kept forever. location_retention_days existed as a setting with no code behind it. The daily job now deletes fixes past their venue's window, defaulting to 30 days where none is set.
- Staff ratings were counted as Google review activity. They now record their own surface and never route to Google.
10 What runs on its own
Two scheduled jobs, one polling loop, and one thing that cleans up by chance. That is the entire list of automation — everything else only happens because a person clicked something.
| Job | When (Brisbane) | What it does | Running? |
|---|---|---|---|
| Daily rating snapshot /api/cron/places |
05:00 every day (19:00 UTC the day before — Vercel schedules are always in UTC) |
Asks Google for the current public star rating and review count of every venue that has a resolved listing, and files one row per venue per day. A failed lookup is skipped rather than written as a zero — a gap in the line is honest, a false 0.0 would look like every customer turned on them. | Skips no Google key |
| Monthly report /api/cron/report |
07:30 on the 2nd (21:30 UTC on the 1st) |
Emails every account owner a per-venue summary: ratings collected, how many went to Google, average stars, how the public rating moved, and the top three themes from their private feedback. Cancelled accounts and venues with no activity are skipped on purpose — a "you got 0" email is how a report becomes the thing that reminds someone to cancel. | Skips no email key |
| Finance dashboard poll | Every 60 seconds, while the page is open | Re-reads the accounting sheet so an edit made on a phone appears on the dashboard within a minute. Pauses when the tab is hidden. | Live |
| Housekeeping | Roughly 1 time in 100, whenever someone taps a star | Deletes flood counters older than a day and expired reset links. Not a schedule — a coin flip. If the product goes quiet, cleaning stops happening. | Live |
11 The money
What we charge, what it costs us, where it is recorded, and the places the numbers currently disagree with each other.
The model in one line
Hardware is the wedge; the subscription is the business. Plaques sell from a published price list plus a flat install — deliberately low friction, because the point of the plaque is to get the subscription signed. Prices are no longer derived from landed cost, so a supplier price change moves our margin rather than silently moving the customer's quote.
| Plaque price list (AUD, per unit) | 1–9 | 10+ | Landed | Margin at 10+ |
|---|---|---|---|---|
| Regular review cards | $15 | $10 | $2.44 | 75.6% |
| Custom review cards — black, white, sand, green | $20 | $15 | $3.19 (green $5.85) | 78.7% (green 61.0%) |
| Installation | $50 flat, once per quote — never per plaque | — | pure margin | |
There is a cliff, and it is worth using. Ten regular cards cost $100, so anyone buying 7, 8 or 9 is paying more than a ten-pack — nine costs $135 for one plaque fewer. Same for custom at 8 or 9 against a $150 ten-pack. The quote builder now says this out loud when it happens: "1 more and every one drops to $10."
Plaques
- Sell
- From the price list above — set prices, not a cost multiple
- Install
- $50 flat, once per job — not per unit
- Bands
- Decided by how many of that family are on the quote; colours count together
- Margin
- Never below 61% on any line at any band, before the install fee is counted
Software — the MRR
- Price
- $149 per venue per month, or $99/month billed yearly ($1,188)
- Unit
- Per venue, not per plaque and not per feature. Reviews, menus or both — same price
- Team
- Free. Only the account owner is billed, however many people have access
- Rule
- Every hardware sale includes it. Non-negotiable
Where the figures live
- Software price
- One constant in the code. Changing it changes the marketing page, the billing page and the legally binding Terms at the same moment
- Plaque price
- Calculated per deal in the quote tools; never published
- Rule
- No plaque price on any public page. Price is disclosed in person or in the contract
What has actually been spent, and on what
Four supplier orders, 505 units, A$1,475.66 — a blended landed cost of $2.92 a unit. Revenue to date is $0, so the current net position is −$1,475.66.
| Order | Date | Supplier | Units | Total AUD | Landed / unit |
|---|---|---|---|---|---|
| 1 — plain review cards | 10 Aug | Leadercolor | 100 | $201.79 | $2.0179 |
| 2 — plain review cards | 10 Aug | Leadercolor | 100 | $286.52 | $2.8652 |
| 3 — custom squares, 100 each black/white/sand | 16 Aug | Huasentong | 300 | $958.08 | $3.1936 |
| 4 — custom squares, green | 18 Aug | Huasentong | 5 | $29.27 | $5.8540 |
| Total | 505 | $1,475.66 | $2.9221 |
Shipping is 29% of everything spent. Freight, not unit price, is the lever worth negotiating. Orders 1 and 2 are still USD estimates converted at 1.43117 — the sheet's own yellow cells flag them. Until they are replaced with the actual AUD charged, every cost, margin and breakeven figure carries a small FX error.
The accounting chain
- One Google Sheet is the book of record.
"Nudge Tags Accounting", in Drive › Nudge Tags › Accounting. Three tabs: Expenses (one row per supplier order, split into goods / shipping / tax / fees), Income (one row per sale), Inventory (each product's landed cost per unit and how many were bought). Receipts sit alongside it as PDFs.
- A small Apps Script hands the sheet over as data.
Guarded by a shared token. It is also what receives a closed sale from the quote builder and writes it into the Income tab.
- The dashboard reads it live.
app.nudgetags.com/finances, superadmin only, refreshing every 60 seconds. Five sub-tabs — Overview, Revenue, Expenses, Units & Inventory, Ledgers — and five time ranges recomputed in the browser, so switching range never disagrees with the server.
- Nothing is ever written back except a closed sale.
If a number is wrong on screen, the fix is always in the sheet.
Inventory (not "inventory", not "Stock") or unit economics silently collapse to zero and margin reads 100%; and software revenue must use a Type containing "subscription", "software" or "saas" with Qty 0, or it books as hardware and skews every per-unit figure. Data-validation dropdowns on both columns would remove the whole class of error.
Two quote tools — and which to use
quote.nudgetags.com Retired & secured
The original standalone calculator. One HTML file, works offline, no login. Quantity, discount and landed cost in; sell price, total, cost, gross profit, margin and a margin-vs-quantity chart out.
- Fixed
- 20 Aug 2026 — the domain and the old project URL both redirect to the login-gated builder. The source file is kept locally and in Drive for reference only
- Also
- The subscription line is stubbed off, so it quotes hardware only — which contradicts the rule that every sale includes the software
- Also
- Install is costed at $0, so margin is overstated by whatever an install actually costs in travel and labour
app.nudgetags.com/quote Behind login
The newer in-app builder. Prices against real stock and real landed costs pulled live from the Inventory tab, shows the margin panel only to us, and logs the closed sale straight into the Income tab — one row per product line, matched by product name so stock-on-hand follows automatically.
- Pricing
- Nothing is typed in. Each product is priced at 3× its own landed cost — rounded up to the nearest 50c, $5 floor — so a $2.44 review card quotes at $7.50 while a $5.85 green custom quotes at $18.00. Pick quantities; every figure follows.
- Mixed orders
- One quote line and one margin row per variant, so a healthy blended margin can't hide one product being sold at almost nothing. Discount applies across the plaques; install is a flat $50 once per quote
- Gate
- Superadmin only
- Safety
- The browser never sees the sheet's write token, and a reference on each sale makes a double-click harmless
- Verdict
- Use this one. Then either lock the public calculator down or retire it
Where the documents disagree about money
These are live contradictions between things a customer could be handed. They are ranked by how much trouble they could cause.
| # | The disagreement | Why it matters |
|---|---|---|
| 1 | GST. The old pitch PDF says prices include GST · the contract says exclusive of GST · the website terms say software pricing includes GST · the quote tool correctly says we are not GST-registered at all | A business that is not registered cannot charge GST. Two customer-facing documents are wrong, and one of them is sitting in Drive › Sales |
| 2 | Lock-in. The pitch says "no lock-in, cancel with 30 days' notice, no exit fee". The contract has a 6- or 12-month minimum with a 50%/30% early-termination fee. The home page says "no lock-in" while its own FAQ describes the minimum term | Handing a venue the pitch and then the contract is a misleading-conduct exposure, not just untidy copy |
| 3 | Who owns the plaque. Pitch: "keep the plaques". Contract: Nudge-branded plaques come back within 14 days or are invoiced. The app's own Terms: "physical plaques are yours to keep" | Three documents, three answers, one of them inside the product a customer logs into daily |
| 4 | What we charge per. Pitch prices per venue tier · contract prices per plaque per month · the software prices per venue. The volume ladder the website promises is written down nowhere | You cannot fill in the contract's fee schedule from any existing price list |
| 5 | Liability cap. Contract says 12 months of fees; the app's Terms say 3 months | A customer who buys both has two contracts and no clause saying which wins |
| 6 | The contract never mentions the software at all | The subscription is the main revenue line and it is not in the agreement that gets signed |
12 The physical product
Exact specification, the colour codes, and the pipeline that turns a Python script into something a factory can quote.
| Round — the general plaque | Square — the review plaque | |
|---|---|---|
| Job | Points at anything: menu, ordering, bookings, wine list, an event, Instagram. No stars, no mention of reviews. | Google reviews only. Five brass stars and "LEAVE US A REVIEW" so a customer knows what it is across the room |
| Size | Ø 90.0 mm | 100 × 100 mm, measured to sharp corners before the R16 radius is taken off |
| Thickness | 3.0 mm cast acrylic, polished edge, R0.5 edge break | |
| Chip | NTAG215 or better — the general plaque does more work | NTAG213 |
| Chip fitting | Ø 25 mm coin tag, surface-stuck on the back, centred. Reads through the 3 mm face — no recess needed | |
| Adhesive | 3M VHB, Ø 86 mm, full base, keep-out over the chip | 3M VHB, 96 × 96 mm, full base, keep-out over the chip |
| Finishes | Sand #E7D9BC (default) · Ink #0B0B0D · Chalk #FFFFFF · Forest #43792C, all with brass #C2A15C accents | |
How a plaque gets made
- Draw it
Python scripts in
nudge-tags/brand/emit the artwork as SVG with all type converted to outlines, so the files open correctly anywhere without the font.make_plaques_all.pyfor round,make_square_reviews.pyfor square. - Export to print resolution
sips -s format png -Z 2126. Never qlmanage — Quick Look pads and crops non-square artwork into a square thumbnail and silently wrecks the drawing. The round master is 600 dpi at Ø90 mm; the square is 540 dpi at 100 mm. - Copy into Drive
Renders › "Circles — General (not reviews)" and "Squares — Review Plaques". Drive is the source of truth, not the repo — a stale local duplicate once shipped an old square design to the live website for hours before anyone noticed.
- Regenerate the spec sheet
A landscape technical drawing with top, side and back views, every dimension, the adhesive area, the chip keep-out and five manufacturing notes. This is the PDF that goes to a factory with an RFQ.
- Rebuild the website textures
python3 website/build_assets.pyreads the print PNGs from Drive and flattens them into the textures the 3D showroom uses — then bumpART_VERSIONin two files by hand or browsers keep serving the old print for up to a year. - Order, write, lock, install
Factory produces; each chip is written with the NFC Tools app and locked; plaque goes on with 3M VHB.
2. The build guide in Drive carries Purple Pi branding — a byline and a purplepimarketing.com link, inside a 50/50 business. It also still instructs printing a QR code on every plaque, which is banned. Both make it stale and wrong; rewrite or delete it.
3. Running either spec generator silently rewrites all eight plaque masters, including four retired designs, because of an import side effect. Check what changed before copying anything to Drive.
4. Nobody but Gauresh can regenerate artwork. The pipeline needs macOS, a specific system font,
sips, and the Drive mount under his account. Leon cannot produce a spec sheet today.
Chips and suppliers
What the chip is
A passive NTAG NFC sticker. No battery, no radio of its own, no certification required — the phone powers it for the instant it is read. This is why there is nothing to charge, nothing to fail and nothing to approve.
Sourcing
- Now
- Complete plaques from Alibaba (Leadercolor, Huasentong) at $2–3 landed
- Local fallback
- Core Electronics, Newcastle NSW — Ø25 mm NTAG213 coin tags around $1.45 each including GST, MOQ 1, in stock
- Benchmark
- ReviewBoost Australia sells one plaque at $69, five at $199
Ignore this file
nudgetags-chip-supplier-research.html is 47 KB of research into ESP32 modules — a powered IoT device this product is not. It has factory lead times, reel minimum-order quantities and compliance requirements that are all irrelevant. Delete it before anyone hands it to a supplier.
13 The commercial layer
The partnership, the contract, how we sell, and who we are selling against.
The partnership, as it stands on paper
What exists Registered
- ABN
- 64 539 870 400 — used consistently in the contract header, all eight website footers and the product's legal pages, all fed from one place in the code
- Minutes
- "Minutes of Meeting Nudge Tags", 16 Aug 2026, DocuSigned by both founders. Records Gauresh Malhotra 50% and Leon Allendes 50%, both as beneficial owners — the declaration banks and AUSTRAC ask for
What does not exist Open
- Company
- This is a general partnership, not a Pty Ltd. Both founders are personally liable — while physically installing objects in commercial venues
- Business name
- "Nudge Tags" is not registered with ASIC. A partnership trading under a non-surname name is required to register it (~$45/yr)
- GST
- Not registered. Correct below the $75k threshold, but three documents currently say otherwise
- Trademark
- No filing, despite the contract and the website both asserting ownership of the name and marks
- Partnership agreement
- The minutes record percentages only. Nothing on profit distribution, drawings, deadlock, IP, buyout, or what happens if one of you can't continue
- Insurance
- No public liability or professional indemnity evidence on file
Fix before the next signature
The DocuSign certificate on the founding minutes records Gauresh's signer title as "CEO, J AND G OPERATIONS PTY LTD" — the wound-down previous business — and the account is still set to US Pacific time. Both are profile settings. Correct them before any customer contract goes out through the same account.
The customer Service Agreement
Seventeen numbered clauses. The canonical copy is in Drive › Nudge Tags › Sales; it is generated by a script, so the way to change it is to edit the generator and re-render, never to hand-edit the document.
| Clause | What it commits us to |
|---|---|
| 2 — Services | Supply and physical install; each plaque programmed to a unique managed link we control; destination changes free, actioned within 2 business days; a monthly report of taps per plaque and the change in Google review count; free replacement of plaques that fail in normal use |
| 3 — The link system | On termination the plaques stop directing anyone anywhere and can no longer be updated. This is the leverage, written down |
| 4 — Fees | All AUD, exclusive of GST. Setup fee payable on signing before installation and non-refundable once produced. Monthly fee per plaque, rate set by total count and by branding choice. Billed monthly in advance, due within 7 days. Every figure is a blank filled in per deal. Service may be suspended if more than 10 days overdue, after notice |
| 4.4 — Price changes | Inside the minimum term, only by mutual written agreement. After it, we may revise on 30 days' notice and the customer may leave fee-free before it applies |
| 5 — Compliance | We state that we do not write, buy, incentivise, filter or gate reviews, and do not screen customers to send unhappy ones elsewhere |
| 6 — Ownership | Nudge-branded plaques remain our property and come back within 14 days of termination, or are invoiced at replacement cost. Custom-branded plaques become the customer's once paid |
| 7 — Term | 6 or 12 month minimum (tick one), then month-to-month. 30 days' notice by text or email. Early termination: 6-month term = 50% of remaining fees; 12-month = 50%, dropping to 30% after month six. Framed as a genuine pre-estimate of costs, which is what makes it enforceable under Australian Consumer Law |
| 12 — Privacy | Tap data is date, time, which plaque and broad device type. No names, phones, emails or location. "Reports contain counts only" |
| 13 — Liability | No guarantee of reviews, rating or ranking; not liable for Google; capped at fees paid in that 12-month period; ACL rights preserved |
| 16 — Law | Queensland. Good-faith discussion, then mediation with costs shared, then the courts |
How we sell
The outreach sequence
Post-visit, three emails: day 0, day 5, day 12. Stop on a reply, stop on a no.
- Day 0
- A real detail from the visit in the first two lines · their review count against the size of the room ("not a service problem, an asking problem") · one line on the plaque · what the software does · the three tiers named but never priced · one call to action: ten minutes in person
- Day 5
- Pre-answers the objection — "we already ask people" — and closes on one question: how many did you actually ask last month?
- Day 12
- Closing your file. Graceful, with a verifiable competitor comparison, door left open
The rules
- One real visit detail, always. Never invent one
- Look up the review count first — the number does the arguing
- Under 250 words, one call to action
- No price in writing. Deflect to in person
- Australian spelling, no emojis, no exclamation marks
- Never claim we block, hide or delete a bad review
- Never claim a specific lift — we do not have that data yet
- Never use the Purple Pi address
Two trackers, and that is one too many
The Outreach Tracker (16 columns, dropdowns, a formula dashboard) is what the script tells you to log into — and it is empty apart from its example row. The real leads live in a different Drive file, "Brisbane_NFC_100_Prospects", with 100 named venues, phones, priorities and a suggested door-knock order across eleven Brisbane areas — and different columns.
- Do
- Pick one before either accumulates history
The competitor: Quick Suite
Around 200 clients across AU, US, CA and NZ, month-to-month with no contracts, built solo by its owner. Broader software than ours — review replies, SEO blogs, social posts and a chat assistant, all branded as named AI characters — and a genuinely strong proof asset in a live client dashboard showing a café going from 28 to 674 reviews. Their hardware is included in the subscription, generic, and the client keeps it on cancellation. They do not do NFC menus.
The honest read from our own dossier: their hardware is worse than ours and their software is deeper than ours. Their exposed flank is compliance — they are actively expanding a "not happy? tell us" route and they generate review text for customers to paste into Google, both of which breach Google's policy and endanger the client's listing, not just theirs. That attack line only works while our own thresholds stay at 1, which is why the threshold question above is a positioning decision rather than a settings detail.
Two notes for the record: the intel came from a call where Leon posed as a buyer for his father's restaurant; and their claim that "47% of people in Queensland use AI to find local businesses" is unverified and probably invented — do not repeat it. Their actual pricing is still unknown, and getting hold of their two emailed proposals remains the single highest-value open item for setting our own price.
14 Playbooks
The dozen things that actually get done, written so either of you can do them without asking the other.
Onboard a new venue, from signed deal to live plaque
- Sign the agreement
Fill in the customer block and the fee schedule in clause 4.2 — branding choice, plaque count, setup fee, rate per plaque, monthly total, start date. Tick the 6 or 12 month term. Both founders sign as Co-Founder. Send as PDF, not .docx: the brand font is not embedded.
- Create their account
app.nudgetags.com/admin→ business name, owner's name, owner's email. You get a setup link that is valid 7 days and shown exactly once. Copy it before leaving the page — there is no resend button, and with email switched off there is no self-service recovery either. - Send them the link
They set their own password and land on onboarding: venue name, Google review link, threshold. Leave the threshold at 1 — everyone goes to Google.
- Set the venue up properly
Paste the Google Maps link in Settings (this is the one that costs a lookup and gives you the address, photo and rating tracking). Set the venue's timezone if it isn't Brisbane. Add the menu link if they're running menus.
- Create a placement per physical object
Reviews tab → Placements. One per plaque, one per table, one per staff member. Each gets its own
?c=code, and that code is the only reason "which table actually gets used" is answerable. - Write and lock the chips
NFC Tools app, one URL per plaque, matching that placement's link exactly. Lock every chip before it leaves your hands — an unlocked tag can be rewritten by any customer with a phone, and a locked wrong URL means physically replacing the plaque.
- Install and test on site
3M VHB, never on metal. Then tap each plaque with a real phone and confirm it opens the right page — not the right venue, the right placement.
- Log the sale
app.nudgetags.com/quote→ build the order → log the close. It writes one Income row per product line and stock-on-hand follows automatically. - Invoice
Manually, for now — Stripe cannot take a card yet. Set a reminder for the recurring charge, because nothing in the system will do it for you.
Ship a change
The product
cd ~/Documents/claude/nudge-tags/nudge-reviews npm run typecheck vercel deploy --prod --yes \ --scope purple-pi-marketing
Then open a review page and the dashboard and click through. There are no automated tests.
The marketing site
cd ~/Documents/claude/nudge-tags/website vercel --prod
If you touched CSS or JS, bump the ?v= number on the link and script tags in all eight pages, or returning visitors keep the old one.
The quote calculator
cd ~/Documents/claude/nudge-tags/quote vercel --prod
And copy the same file over the Drive copy, or the two drift apart.
/assets/img/ — it is cached in visitors' browsers for a year and marked immutable; save it under a new filename instead.Change the database
- Write a new numbered migration file
In
db/migrations/. UseADD COLUMN IF NOT EXISTS/CREATE TABLE IF NOT EXISTS— every file is re-run on every migration, so it has to be safe to repeat. Never put a bare UPDATE in one; one existing migration does that and it silently re-grants access every time it runs. - Update
db/schema.sqltooIt is what a fresh database is built from. It is currently missing two tables that live only in migrations — don't add a third.
- Run it against production, by hand
cd ~/Documents/claude/nudge-tags/nudge-reviews DATABASE_URL="<pooler string>" npm run db:migrate
Nothing runs this automatically. Deploy the code only after the migration has run, or the site crashes for everyone in between.
Rotate a key
| Key | How | Blast radius |
|---|---|---|
| AUTH_SECRET | New random value in Vercel, redeploy | Everyone is signed out everywhere, including you. Passwords still work. Unique-visitor counts wobble for that day, so do it late at night |
| FINANCE_FEED_TOKEN | Change it in two places together: Apps Script → Project Settings → Script Properties, and Vercel. Then redeploy | Finance dashboard says "Feed refused" until both match |
| DATABASE_URL | Rotate the password in Supabase, update Vercel, update .env.production.local, redeploy | Everything is down between the rotation and the redeploy. Do it deliberately |
| GOOGLE_PLACES_KEY | New key in Google Cloud with the same restrictions, swap in Vercel, delete the old one | Photos and rating snapshots pause until the redeploy |
| SUPERADMIN_EMAILS | Edit in Vercel, redeploy | Grants or removes the operator console, the finance dashboard and venue deletion — all at once |
Switch email on — the highest-value hour available
- Create a Resend account and add nudgetags.com as a sending domain.
- Add the SPF and DKIM records at GoDaddy
Alongside the existing Google Workspace records, not instead of them. Wait for Resend to show the domain verified.
- Set
RESEND_API_KEYin Vercel and redeploy.CRON_SECRETis already set, so the monthly report will not be blocked. - Test all four
A password reset to yourself; leave a 1-star rating on a test venue and check the alert lands; run the report job manually; create a test account and check the welcome email.
- Then tell customers about it
Password self-service, feedback alerts and the monthly report all start working at once — and the monthly report is the single best anti-churn asset in the product.
Take the first real payment
- Activate the Stripe account
Business verification and bank details. Gauresh only — never hand these to anyone else, and never enter them on someone's behalf.
- Configure Stripe Tax with an AU registration
The code requests automatic tax on every checkout. Without this configured, creating a checkout session fails outright.
- Recreate the product and both prices in live mode
$149/month and $1,188/year, AUD, recurring, priced per unit so the quantity equals the venue count.
- Recreate the webhook in live mode
Pointed at
https://app.nudgetags.com/api/stripe/webhook, subscribed to the six subscription and invoice events. - Set four variables in Vercel and redeploy
The secret key, both live price IDs, and the webhook signing secret. Until all four are set the billing page correctly says card payments aren't switched on.
- Mark your own accounts exempt
Founder accounts are flagged by a database column and there is no admin screen for it — it needs a one-line SQL update against Supabase. Do this before testing, or you will invoice yourselves.
Monthly finance close
- Enter every supplier order
One row per order in the Expenses tab, above the blank row so the sheet's own totals still cover it. Split the cost into goods, shipping, tax and fees — the grey cells are formulas, don't type over them. Category must read exactly
Inventory. - Replace any FX estimates
Yellow cells are USD converted at an assumed rate. Replace them with the actual AUD your statement was charged. Orders 1 and 2 are still outstanding.
- File the receipt
Drive › Nudge Tags › Accounting › Expenses, named to match the order.
- Check every sale is on the Income tab
Sales closed through the quote builder land automatically. Anything invoiced by hand needs a row — with Qty 0 for software-only revenue and a Type containing "subscription", or the unit economics skew.
- Add any new product to the Inventory tab
Product, type, description, supplier, landed cost per unit, units bought. The product name has to match what the quote builder sells, because that name is what links stock to sales.
- Read the dashboard
/finances→ Overview for net position and cash recovered, Units & Inventory for stock on hand and the stock-out horizon, Expenses to see whether freight is still 29% of spend.
Something is broken — triage in order
- Can you load a plaque link?
app.nudgetags.com/r/<a real slug>. If yes, customers are fine and this is not an emergency. - Check the operator console
/adminshows the last 25 crashes with the path and the affected user, plus a 24-hour count. Start here before guessing. - Marketing site fine but nobody can log in?
That is the specific signature of a broken
AUTH_SECRET. - Everything slow?
Check Supabase is up and that the Vercel functions are still pinned to
syd1. Cross-Pacific queries once made the whole product feel broken. - Dashboard fine, finance page dead?
The Apps Script deployment URL or the token has changed. Both halves of the token have to match.
- Check Vercel's own logs
Deployment logs for build failures, function logs for runtime errors, cron logs to see whether the scheduled jobs actually ran.
Give Leon access
Today he has none of the operator surfaces. To change that: he needs a user account in the product, and his email added to SUPERADMIN_EMAILS in Vercel, followed by a redeploy. Be deliberate — that one variable grants the operator console over every customer account, the real revenue figures and the ability to permanently delete a venue. There is currently no way to grant the finance dashboard alone; if that separation matters, it has to be built. He will also need: the Vercel team, the Supabase project, the Drive folder, the GoDaddy login and the Stripe account — none of which he can be given through the product.
When a customer cancels
- Check the term
If they are inside a 6 or 12 month minimum, the early-termination fee applies — 50% of remaining fees, or 30% after month six on a 12-month term. Notice is 30 days and does not shorten the minimum term.
- Do not delete anything
Set the venue to paused. Deleting erases every tap, rating and complaint permanently and cannot be undone. Pausing keeps the plaques valid if they come back.
- Recover the plaques
Nudge-branded plaques are our property and are due back within 14 days, or invoiceable at replacement cost. Custom-branded plaques stay with the customer.
- Stop the billing
Manually, since Stripe isn't live. Once it is, cancelling the subscription flips the account automatically and the links go dark on their own.
15 What has to be revisited, and when
Nothing on this list is urgent today. Everything on it becomes expensive if it is never done.
| When | Check | Why it is on the list |
|---|---|---|
| Weekly | The operator console error log | It is the only place a crash surfaces. Nothing alerts anyone |
| Weekly | Private feedback across all venues | Alert emails cannot send yet, so a complaint sits unread unless someone looks |
| Weekly | Submit the enquiry form on nudgetags.com and confirm the email arrives | The free relay can silently stop delivering while still showing the visitor "Got it" |
| Monthly | Finance close — see the playbook | The books are only as good as what was typed in |
| Monthly | Google Cloud billing and quota, once a key exists | One billed call per venue per day; the cost grows with the customer count and nothing caps it |
| Monthly | That the two scheduled jobs actually ran | A failed run is silent — no retry, no alert, nothing in the error log |
| Quarterly | Tap a real plaque and confirm Google's review box still opens | The deep-link format is undocumented. If Google changes it, every plaque on every counter quietly stops converting |
| Quarterly | Read the marketing site, the FAQ, the contract and the app's Terms side by side | They currently disagree about GST, lock-in, plaque ownership and the liability cap |
| Quarterly | Who is in SUPERADMIN_EMAILS, and who has Drive, Vercel, Supabase and GoDaddy access | Access accumulates. This is also the list to check the day anyone leaves |
| Quarterly | Supplier pricing and freight | Shipping is 29% of spend and the last quotes are from August |
| Yearly | Domain renewal and the card on file at GoDaddy | A lapsed domain takes the site, the app, the plaques and the email at the same moment |
| Yearly | The Stripe API version pinned in the code | Stripe deprecates versions; ours is pinned deliberately and will need moving |
| Yearly | Legal page dates and the privacy claims | Three pages carry a hardcoded date. The privacy page says "the two of us" — which stops being true the first time a contractor gets access |
| Yearly | Cached Google addresses and photos | Nothing re-syncs them. A venue that moves keeps its old address forever |
| At $75k turnover | GST registration | Then flip the GST flag in the quote tool, and fix the three documents that already claim GST is included |
| Before venue #100 | The daily rating job's 60-second budget, and the monthly report's lack of a send log | Both work fine now and both fail quietly at scale |
| On any suspicion | Rotate AUTH_SECRET | It is the only way to sign a stolen session out. There is no device list and no per-user revocation |
16 Open items, worst first
Everything found in this audit that a founder would rather know now than discover later. Nothing here is on fire; several things would be, given time.
Do first
| What | Why it matters | Effort |
|---|---|---|
| Put the code in a private GitHub repo | There is no version control anywhere. No history, no undo, no way to see what changed between two deploys, and the only copies are one Mac and a Drive backup of it. Everything else on this page is recoverable; this is the one that isn't. | 1 hour |
| Check whether the Supabase tables are publicly readable | Supabase exposes tables in the public schema over its own API by default, and none of our 13 tables have row-level security. The app never uses that path — it connects directly — so this has simply never been thought about. If the table editor shows "Unrestricted", customer data may be readable, and writable, with a key that is public by design. Verify before anything else on this list. | 15 min to check |
| A redirect now exists in the product's config sending that hostname to the operator-only builder — but it cannot fire, because the domain is still attached to the old nudge-tags-quote project (verified 19 Aug). So the public calculator is still live, with no login and no noindex, displaying our landed cost, our markup and our margin to anyone with the link. Detach the domain from that project and attach it to nudge-reviews, then the redirect takes over. | 10 min | |
| Strip Purple Pi purple out of the Build Guide | The shipped "Build Guide and Shopping List" document — in Drive where Leon can open it — contains 23 occurrences of the PPM brand purple, set by the two scripts that generate it. That is a direct breach of the rule that nothing Nudge-facing carries a Purple Pi mark, inside a 50/50 business. The same document also carries a purplepimarketing.com link and still instructs printing QR codes. | 20 min |
| Retire the old pitch PDF | It is in Drive › Sales, it prices $49/$99 tiers that no longer exist, says prices include GST when we are not registered, promises no lock-in against a contract with an early-termination fee — and it tells the customer "they cost us a few dollars each". That last line is an unforced margin disclosure in a sales document. | 5 min |
| Delete the runaway line in migration 006 | It was written as a one-time back-fill but migrations are re-run every time, so it now silently promotes any correctly-restricted team member to see every venue in their business — no log, no symptom. | 5 min |
| Fix the menu links on venues that run both | A purpose-built menu route exists and works, but nothing links to it — every table QR is generated pointing at the review path. On a venue running reviews and menus, a customer scanning a table code gets the five-star page instead of the menu, and the tap is filed as review traffic. Menu-only venues are unaffected, which is why it hasn't been noticed. | Small code fix |
| Fix the "Edit access" form | It never writes the field that controls whether a member sees all venues. Change someone from one venue to "all" and they end up seeing nothing, with no explanation; change them from "all" to one venue and they keep seeing everything while the screen says they're restricted. The second one is a real privacy failure in exactly the multi-venue scenario the feature is sold on. | Small code fix |
| Test the enquiry form end to end | The relay needs a one-time activation link clicked in the mailbox, and an un-activated or throttled form still tells the visitor "Got it" while the enquiry disappears. Every web lead depends on this and nobody has confirmed one has ever arrived. | 5 min |
| Switch email on | No password recovery for any customer, no feedback alerts, no monthly report — the report being the single best anti-churn asset in the product, which has never sent once. | 1 hour |
Fix this month
| What | Detail |
|---|---|
| Reconcile the money documents | GST said three different ways, lock-in contradicted between the pitch and the contract, plaque ownership answered three ways, and no written price ladder to fill the contract's fee schedule from. Retire the August pitch PDF in Drive › Sales — it is the oldest and the wrongest. |
| Decide the gating policy | The contract promises we do not screen unhappy customers away; the product ships a threshold that does exactly that when set above 1. It defaults to 1 and the site is open about it, so the position is honest today — but it is a setting, not a guarantee, and it is also the main line of attack against the biggest competitor. Make it a decision. |
| One default for the star threshold | Six code paths currently disagree — three default to 1, three to 4 — and one update path silently falls back to 4, so a partial save can re-arm gating on a venue the owner set to 1. |
| Fix the four dead "Get started" buttons | The product's own marketing page sends every sign-up click to a page that says you cannot sign up. That is a straight conversion leak on a live page. |
| Fix the broken social preview image | Five of the eight marketing pages point at an image that does not exist, so sharing them anywhere shows nothing. One line each. |
| Fix the circle spec sheet's chip | It says NTAG213; the round plaque is meant to be NTAG215 or better. This is the drawing that goes to suppliers. |
| Clean the Drive build guide | It carries Purple Pi branding and a purplepimarketing.com link inside a 50/50 business, and still instructs printing a QR code on every plaque. |
| Rename the Drive "Review Software" folder | It shows a dead Cloudflare build at the top level with the real product buried underneath. Anyone opening it reads the superseded version first. |
| Pick one outreach tracker | The script says log in one file; the 100 real leads live in another with different columns. |
| Register the business name with ASIC | A partnership trading under a non-surname name is required to. About $45 a year. |
| Write an actual partnership agreement | The signed minutes record 50/50 and nothing else — no profit distribution, no deadlock, no IP, no buyout, no incapacity. Also worth pricing public liability insurance, given you physically install objects in commercial premises as personally-liable partners. |
| Fix the DocuSign profile | It stamps Gauresh's title as CEO of the wound-down previous company onto documents, and the account is set to US Pacific time. |
Know about it
| Item | Impact |
|---|---|
| The Google rating panel renders twice on the Overview when a single venue is selected | Cosmetic, but it is the first thing a customer sees |
| Owner previews inflate the owner's own numbers | Tapping your own plaque in preview is excluded from tap counts but not from the average rating, happy percentage or "sent to Google". The public badge excludes them correctly, so the two disagree |
| The period filter does nothing on the private feedback tab | It shows the most recent 200 regardless of the 7/30/90 selection sitting above it |
| The feedback CSV is hardcoded to Brisbane time | Wrong for any interstate venue, in a product that is otherwise careful about timezones |
| Unique visitors roll over at UTC midnight, but the day column rolls at the venue's midnight | In Brisbane that is 10am, so a customer visiting at 9 and at 11 counts as two people |
| Appending ?c=preview to any plaque link makes that rating vanish | An open suppression switch. Also means a venue must never name a placement "preview" |
| A cancelled venue's public ratings badge keeps working | Their review pages go dark; the badge on their website does not |
| The locked-tab "Set up" button can dead-loop | If a capability was explicitly switched off, the setup form saves the link but never flips the switch, so you land back on a locked tab |
| A never-invoiced account can run three live venues indefinitely | The default venue limit is 3 and an account with no plan stays fully live — deliberate, to cover the gap between an in-person sale and billing, but it means nothing chases an unpaid account |
| Stripe's "incomplete" status maps to "payment failed" | A customer part-way through card verification would see a red "your last payment failed" banner before they had ever paid |
| Taps, ratings and complaints are never deleted | Including any contact detail a customer typed into a feedback form. The privacy pages promise retention windows that nothing enforces |
| One analytics query can never return a row | The "runs both / reviews only / menu only" mix on the Overview filters on the wrong value, so that panel is permanently empty |
| Marketing site has no analytics at all | Deliberate and honest — and it means there is zero measurement of traffic or form conversion. Worth being a choice rather than an accident |
| A domain we may not own is baked into a print script | nudgetags.link is hardcoded as the QR base in the card-printing script. Nothing live uses it, but anything printed from that script would point at a domain that does not resolve |
| Leon cannot regenerate any artwork | The Drive Brand folder has only one of the six generator scripts. The plaque and spec-sheet generators exist on Gauresh's Mac alone, and need macOS, a specific system font and the Drive mount under his account |
| A stray Supabase link file names a different project | The folder carries a leftover command-line link to a second, probably abandoned Supabase project. Anyone running a Supabase push from that folder would target the wrong database. Delete the folder or re-link it |
| Personal data sits in a personal Drive | The signed partnership minutes carry both founders' home addresses and dates of birth, in Drive under one personal Google account |
| All three websites bill to the Purple Pi Vercel team | Accepted deliberately — a second team needs a payment method and nothing customer-visible carries the name — but it does mean a 50/50 business's entire hosting sits in one partner's other company, with no access for the other |
| Two dead codebases still on disk | An early redirect-only Cloudflare worker and a full Cloudflare version of the review product. Neither was ever deployed; both have placeholder credentials. Salvage the QR encoder and the compliance write-up, then delete |
17 Where everything lives
Which file, which folder, which document. Start here when you can't remember where something is kept.
On the Mac
Everything is under ~/Documents/claude/nudge-tags/. This folder is backed up to Google Drive and is not in version control.
| Folder | What it is | Status |
|---|---|---|
| nudge-reviews/ | The product. Next.js app behind app.nudgetags.com — dashboard, tap pages, operator console, finances, quote builder | Live |
| website/ | The marketing site. Eight hand-written HTML pages plus the WebGL showroom | Live |
| quote/ | The standalone quote calculator, one HTML file | Being replaced |
| ops/ | This page | Live |
| brand/ | Brand marks and the plaque artwork generators, plus the brand guide | Live |
| renders/ | The hand-written 3D renderer that makes the product images | Live |
| agreement/ | The rendered Service Agreement | Live |
| nudge_contract.js | The contract generator. Edit this, never the document | Live |
| outreach/ | The three-email post-visit sequence and its rules | Live |
| pitch/ | The old venue pitch deck | Retire it |
| cards/ | Print sheet for insert cards, for the retired stand format | Dead |
| worker/ | The first redirect-only Cloudflare build. Never deployed | Delete |
| nudge-review/ | The full Cloudflare version of the review product. Never deployed. Worth salvaging its QR encoder and its compliance write-up first | Delete after salvage |
| …/nudge-tags-accounting/ | A stale local export of the accounting sheet. Not the book | Ignore |
In Google Drive — BUSINESS › Nudge Tags
| Folder | What is in it |
|---|---|
| Accounting | The accounting sheet — the book of record — and the supplier invoices |
| Brand | Logos in SVG and PNG, the palette, the brand guide. Missing five of the six generator scripts |
| Renders | The source of truth for artwork. "Circles — General (not reviews)", "Squares — Review Plaques", "Colour Codes". Print masters, spec sheets and 3D renders |
| Sales | The Service Agreement, the (retire-it) pitch PDF, the outreach script |
| Legal | The signed partnership minutes and the DocuSign certificate |
| Website | A copy of the marketing site and the links-and-hosting document |
| Review Software | Misleading. A dead Cloudflare build sits at the top level with a stale copy of the real app underneath. Rename it |
| Quote Calculator | A copy of the standalone calculator |
| Build | The build guide — the one with Purple Pi purple in it |
| Operations | This manual and its supporting documents |
· Nudge Tags — Website, Links and Hosting
· Nudge Tags — Email Outreach Script (Post-Visit)
· Quick Suite vs Nudge Tags | Pricing & Strategy Comparison
· Quick Suite | Competitor Dossier
18 Change log
What changed in this manual, and when. Append a line here every time it is updated — a manual nobody trusts the date on is a manual nobody reads.
| Date | Change |
|---|---|
| 20 Aug 2026 | quote.nudgetags.com secured. The domain was moved onto the app, so its redirect to the operator-only /quote builder now fires — every path lands on the admin login. The retired nudge-tags-quote project, which was still serving the old calculator publicly on its own vercel.app URL complete with landed cost and the 3× multiplier, now serves a stub with no pricing in it that redirects the same way. Verified: both URLs 307, and neither response contains cost or markup. |
| 20 Aug 2026 | Placeholder URLs highlighted, with worked examples. Every URL containing a per-venue placeholder is marked in amber in section 04 and carries a real example — four of which are live links to the demo venue. |
| 20 Aug 2026 | Added "Every URL, complete" (section 04) — every public page, customer link, signed-in screen, operator surface, machine endpoint, redirect and console in one place, including the two generated pages a close produces (/welcome/<slug> and /thanks/<slug>) which had never been written down. The standing rule is stated at the top of that section: a new URL gets recorded here and in the Drive links document in the same session. |
| 20 Aug 2026 | Plaque prices moved to a published price list. Regular review cards $15, or $10 at ten and up; custom review cards $20, or $15 at ten and up; installation unchanged at $50 flat. Prices are no longer derived from landed cost — a supplier price change now moves our margin instead of the customer's quote. The band is judged on the family total, so colours count together. The quote builder shows the full price list on screen, marks which band each line landed in, states the bulk saving in dollars, and prompts when one or two more units would drop the whole family to the next price. The old standalone calculator now carries a do-not-quote banner. |
| 19 Aug 2026 | Finances tab state now survives a refresh, and the load got faster. The tab, time range and ledger search ride in the URL (?tab=revenue&range=6m), read on the server so a reload paints the tab you were on rather than flashing Overview. The wait was never the loading screen — it is one call to the Apps Script feed — so a fetched payload is now reused for 8 seconds, which makes a refresh, and the hop between /finances and /quote, effectively instant. The 60-second poll always reads fresh. |
| 19 Aug 2026 | Quote builder repriced per variant. Price per plaque and the install fee are no longer input fields — each product is priced from its own landed cost (3×, rounded up to 50c, $5 floor), the quote shows a line per selected variant, and the margin panel breaks down profit and margin per variant. Sale rows written to the Income sheet now carry each variant's actual price rather than a pro-rata share of a blended one. |
| 19 Aug 2026 | First full audit and first version of this page. Every codebase, asset, Drive folder, live surface, environment variable and business document read end to end. Verified live: DNS records, Vercel projects and their production environment variables, the accounting sheet's current shape, and which project owns each domain. |
~/Documents/claude/nudge-tags/ops/parts/. Edit a part, run ./build.sh, then vercel --prod. Add a line above whenever you do. If a section is out of date, it is worse than missing — say so in the text rather than leaving it looking current.