LET
Find a game fast
Enter the password to view the playbook.
← LET hub
LET
Find a game fast
Tennis matchmaking,
reimagined for the
casual player.

LET is a Tinder-style app that connects tennis players at the right level, in the right location, at the right time — so you spend less time searching and more time on the court.

iOS App
FlutterFlow + Firebase
Melbourne · Australia
MVP in Progress
Tagline Options Under Consideration
A LET Tennis — Find a game fast
B LET — Find a game fast
C LET Tennis — Tennis partners, fast
D LET — Find your next game
E LET — Play more tennis
Document type Business Partner Brief
Date May 2026
Version 1.0 — Confidential
Build Progress · as of 6 June 2026
~59 hours across ~24 sessions
Phases 1–6 complete · Phase 7 (Matches & Chat) in progress — the core loop (discover → match → chat) is live and demoable.
By phase: Phase 3 SwipePage ~5–6h · Phase 4 Mutual Match ~3 sessions · Phase 5 Profile & Settings ~45 min · Phase 6 GAMEON ~7h · Phase 7 Matches & Chat ~34h and counting (22 May–5 June — ~2h a night early on, ~4h the last two nights). Phases 1–2 (Firebase foundation + profile) were built earlier and not separately timed, so the true total is a little higher. 5–6 June added infrastructure + research rather than new app-build phases: the domain lettennis.app was bought and wired, a server-side Cloudflare Access login was stood up over the hub, and an 18-agent competitor + Apple-submission audit was run.

What is LET?

LET is a mobile app that lets tennis players discover, match with, and message other players at their skill level — the same way a dating app connects people. You swipe on player profiles. When two players both swipe right on each other, it's a match. From there, you chat directly inside the app and arrange a time to play.

The core loop

Sign upBuild your profileDiscover playersSwipe right to likeMutual matchChatBook a game

Core Features (MVP)

Email / Password Auth

Secure sign-up and login. User profile created on first sign-in.

Player Profiles

Name, age, suburb, city, club, bio, skill level, match type, availability.

Swipe Discovery

Full-screen player cards with Pass and Like buttons. Swipe gestures supported.

Mutual Match Detection

When both players like each other, a match is created automatically.

Match Modal

A match screen appears when two players like each other. Jump straight into chat from there.

In-App Chat

Direct messaging per match. Arrange the game without leaving the app.

Why LET — The Gap in the Market

Existing tennis apps fall into two categories: structured league platforms (UTR, Tennis Australia's competition system) or court booking tools (CourtReserve, ClubSpark). Neither solves the most common problem casual players face — finding someone to hit with this week. LET sits in the space no one has properly built for: spontaneous, social, player-to-player matchmaking. The Tinder mechanic is deliberately chosen — it's a pattern millions already understand, applied to a sport context for the first time at consumer scale in Australia.

MVP Success Metrics
  • ✓ Two test accounts can sign up, match, and chat end-to-end
  • ✓ App passes Apple TestFlight review
  • ✓ App passes public App Store review
  • ✓ 20+ real Melbourne players active in beta
  • ✓ At least 5 games arranged through the app in beta period
Monetisation Roadmap

MVP launches free — the priority is users and validation, not revenue. Post-launch options include:

  • → LET Premium — unlimited swipes, profile boost, read receipts
  • → Club partnerships — featured placement for tennis clubs
  • → Court booking affiliate — commission on bookings made post-match

What LET Is Not

Staying focused on the core problem means saying no to a lot of adjacent ideas. LET is deliberately not any of the following:

A league or competitive ranking platform
A court booking platform
A social media feed or content app
A coaching or training app
A tournament organiser
A stats tracker (wins/losses deferred)
A group organiser (1-on-1 only at launch)
A replacement for UTR or club systems

Tech Stack

LET is built on a modern, low-code foundation that allows rapid iteration without sacrificing reliability or scalability. Every component is a managed service — there are no servers to maintain.

Frontend
FlutterFlowVisual app builder on top of Flutter. Produces native iOS code. Handles UI, navigation, Firestore queries, and user actions without custom code.
Auth
Firebase AuthenticationEmail/password sign-in. Each user gets a unique UID used as their document identifier throughout the database.
Database
Cloud FirestoreGoogle's scalable NoSQL document database. Real-time updates, offline support, and fine-grained security rules.
Distribution
Apple App StoreiOS first. Android deferred to post-MVP. TestFlight for beta testing before public release.
Hosting
NetlifyStatic hosting with one-command CLI deploys. Serves the partner playbook and the interactive prototype. Personal plan — $9/month (from June 2026).

Data Model

Four Firestore collections power the entire app:

users

FieldType
uidString — Firebase Auth UID
display_nameString
ageString
suburb / cityString
clubString (optional)
bioString
levelBeginner / Intermediate / Advanced / Elite
matchTypeSingles / Doubles / Both
availabilityList — Mon–Sun
photo_urlString deferred

matches

FieldType
user1IdString — UID
user2IdString — UID
createdAtTimestamp

swipes

FieldType
swiperIdString — UID of swiper
swipedIdString — UID of target
direction"left" (pass) or "right" (like)
timestampTimestamp

messages (subcollection of matches)

FieldType
senderIdString — UID
textString
timestampTimestamp
Security

Firestore security rules govern every read and write: profiles are editable only by their owner, swipes are write-once by the swiper, and match records are readable only by the two matched players. As part of the current safety phase — before any TestFlight build — conversation messages are being locked to match participants only, with Firebase App Check and authentication hardening (email-enumeration protection, password policy) added on top.

Build Phases

LET is built one phase at a time. Each phase is completed and tested before the next begins — this keeps the codebase stable and the product shippable at every milestone.

Development Principles

LET is built with a clear philosophy: ship a focused, working product fast — then iterate. These principles govern every decision made during development, from platform choice to feature scope.

One platform, done right

We build iOS first on FlutterFlow with Firebase as the backend. No platform sprawl, no framework switching mid-build. This constraint keeps the team fast, the codebase simple, and the product polished before we expand to Android.

Simplicity is a feature

When two approaches solve the same problem, we pick the simpler one. We avoid custom code unless FlutterFlow has no built-in solution. A maintainable app is a shippable app.

One phase at a time

We complete and test each phase before starting the next. There is no "we'll fix it later" — if something breaks, it gets fixed before moving forward. This keeps the product stable at every milestone.

Scope discipline

Features not in the PRD do not get built. Deferred features stay deferred until the MVP is complete and tested. Every addition must be explicitly approved against the product vision.

Data integrity & security first

All Firestore writes are authenticated and scoped to the user's UID. Security rules are written and published before any data is exposed. No sensitive data is stored in plain text. Users can never see or modify records they don't own.

Functionality before aesthetics

Branding and visual polish happen last. We build flows that work correctly, then make them look premium. This avoids the common trap of a beautiful app that doesn't function reliably.

Documented as we go

The PRD, Architecture doc, Build Plan, and AI Rules are living documents — updated whenever significant progress is made. Any AI-assisted session begins by referencing these documents to maintain continuity and context.

Real data only

No placeholder or dummy data appears in production flows. Every piece of content shown to a user comes from a real Firestore document. This ensures the product is always representative of the real experience.

Live prototype:  lettennis.tiiny.site  ·  Target market: Melbourne, Australia  ·  MVP target: App Store submission, two accounts confirmed end-to-end

Built secure, not secured later

Security is treated as a feature, not a final checklist. LET's defence sits on the backend, where it cannot be bypassed: Google's Firestore Security Rules decide every read and write and are enforced on Google's servers — never in the app, where a determined user could otherwise reach around the interface. Below is what protects the app as it is built, the plan for users' information, and the plan for money once LET monetises.

1 · Securing the app as we build

Participant-only conversations

Messages and match records are locked so they are readable and writable only by the two matched players — enforced by Firestore Security Rules on Google's servers, not the app interface, so no one can reach another player's conversation through the data layer. (Rules published and live as of 5 June; final end-to-end chat re-test pending a swipe-flow fix.)

Authentication hardening

Enforced password complexity and minimum length are live, with email-enumeration protection being switched on so the login flow cannot be used to discover who holds an account.

Firebase App Check

Enabled at the TestFlight stage to block bots and scripts from hitting the backend directly, so only the genuine LET app can reach the database.

Safety & moderation

In-app reporting of objectionable content is live and reviewed through a moderation queue; block and unmatch are being added alongside it — meeting Apple's user-generated-content requirements.

No secrets in the app

No private keys ship inside the app. Sensitive operations run on managed Google infrastructure, with data encrypted in transit and at rest by default.

Gated pre-launch materials

This playbook and the live demo are password-gated, marked confidential, and have their access rotated, so the build and its IP are not openly accessible.

2 · Protecting users' information

3 · Protecting money (when LET monetises)

LET launches free. When Premium subscriptions and court-booking commission arrive, payment security is designed in from the start:

Protecting the idea

The moat is execution, but the plan protects the name and the work too: trademarking LET and the logo (IP Australia), a confidentiality / NDA step before the build is shown to partners, and a written IP-assignment from any collaborator who touches the code.

Phase 3 — Build Report

SwipePage · 10 May 2026

Time to Complete
~5–6 hours (original estimate: 30 minutes)

What the App Can Now Do

🃏
Real player cards
Live profiles pulled from Firestore, displayed in a swipeable card stack
👆
Two ways to swipe
Users can manually swipe cards or tap the X / ✓ buttons — both work identically
🗄️
Swipe recording
Every pass or like is written to Firestore with correct player IDs, direction and timestamp
🎯
Ready for match detection
Data structure in place to detect mutual likes in Phase 4

Key Challenges Encountered

⚠️
AI Generation Failures
FlutterFlow's AI ignored critical widget requirements 3 times despite explicit instructions, requiring full manual rebuild
⚠️
Undocumented Platform Constraint
SwipeableStack widget only accepts a single child — discovered mid-build, required architectural pivot for button placement
⚠️
Data Access Architecture
Buttons placed outside the SwipeableStack couldn't read current player data — solved by wiring buttons to trigger swipe gestures instead of writing to Firestore directly
Key Insight
These challenges are typical of first-time builds on any platform. The architectural patterns discovered in Phase 3 are now understood and documented — Phase 4 onwards will move significantly faster.

Phase 4 — Build Report

Mutual Match Detection · 11 May 2026

Time to Complete
~3 sessions across 2 days (completed & tested 16 May 2026)

What Was Built

🔀
Direction tracking fixed
Swipe direction was hardcoded as "left". Now correctly reads "left" or "right" from App State before writing to Firestore
🔍
Mutual match query
On every right-swipe, Firestore is queried for a reciprocal right-swipe using 3 filters (swiperId, swipedId, direction)
💾
Match document creation
When a mutual match is found, a document is written to the matches collection with user1Id, user2Id, and server timestamp
🎾
Match alert
"It's a match! 🎾" alert dialog fires when mutual match is detected. Placeholder for the full match modal in Phase 5

Key Challenges Encountered

⚠️
Doc Reference vs String type mismatch
matches collection fields were created as Doc Reference type, causing User ID to be greyed out. Fixed by deleting and recreating as String fields
⚠️
Action Flow Editor — no "Insert Before"
FlutterFlow has no way to insert an action before an existing one. Required deleting and rebuilding button action flows in the correct order
⚠️
Routing sent users to old test page
"Navigate Automatically" on login buttons was routing to an old HomePage. Fixed by disabling auto-nav and manually routing Sign In → SwipePage and Create Account → CreateProfile
Phase Complete — Test Results
End-to-end test passed 16 May 2026. Two accounts (Safari + Safari Private) both swiped right on each other. Match document confirmed created in Firestore with correct user1Id, user2Id, and server timestamp. "It's a match! 🎾" alert dialog fired on both sides. Phase 4 is fully complete.

Phase 5 — Build Report

Profile & Settings Page · 17 May 2026

Time to Complete
~45 minutes (original estimate: 30 minutes — dinner break included)

What Was Built & Tested

📋
ProfilePage created
Duplicated from CreateProfile — saved layout time and guaranteed visual consistency across the app
🔍
Firestore backend query
Users collection → Single Document → uid Equal To Authenticated User ID. Wired to the page Column
✏️
All fields pre-populated
display_name, age, suburb, city, club, bio — each TextField Initial Value set from users Document
🎯
Chips pre-populated
Skill Level, Match Type, and Availability ChoiceChips all load saved values via Initial Option from users Document
💾
Save Changes button
Update Document action — maps 9 fields (display_name, age, suburb, city, club, bio, level, matchType, availability) to their widget values
🚪
Log Out button
Firebase Auth Log Out → Navigate to LoginPage (Replace Route). Tested — lands correctly on login screen and clears session
🧭
Navigation wired
Person icon in SwipePage bottom nav bar wired to Navigate To ProfilePage — one tap from anywhere in the app
End-to-end tested
Logged in → navigated to ProfilePage → confirmed data loaded → tapped Log Out → confirmed landing on LoginPage
Approach That Saved Time
Rather than generating a new page from scratch, Phase 5 was built by duplicating CreateProfile. The layout, form fields, chip selectors, and visual style were inherited instantly — the only work was rewiring the data direction (read instead of write) and swapping Create Document for Update Document. Phase 6 (Match Page) is next.

Phase 6 — Build Report

GAMEON Match Page · 17–18 May 2026

Time to Complete
~7 hours across 2 sessions (3h yesterday · 4h tonight)

What Was Built & Tested

🤖
GAMEON page AI-generated
FlutterFlow AI gen produced 95% of the layout from a single text prompt — split-screen photos, dark overlay card, headline, buttons
📝
Copy locked sport-first
"GAME ON", "Plan a Hit", "Find More Players" — deliberately not "MATCHED!" or "Keep Swiping" to avoid dating-app tone
🔗
Three page parameters wired
matchedUserName, matchedUserPhoto, matchedUserId — passed from SwipePage via Swipable Stack Current Element
🖼️
Split-screen photos working
Right = matched player's photo. Left = current user's photo via backend query on users collection
🧬
Dynamic subtitle
"You and [matchedUserName] are ready to hit the court" — Combine Text widget mixes static text with the live name variable
🎾
Plan a Hit button
Stubbed for Phase 7 with "Coming soon" snackbar. Will navigate to chat thread once chat is built
↩️
Find More Players
Plain text link → Navigate Back. Returns user to SwipePage exactly where they left off
🔁
SwipePage rewired
Replaced the Phase 4 "It's a match!" alert dialog with Navigate To GAMEON action — passes the swiped user's display_name, photo_url, and uid

Key Challenges Encountered

⚠️
FlutterFlow web preview blocks external images
Pravatar, Firebase Storage, and Unsplash URLs all hit CORS errors in the web preview. Solved by hosting photos on Imgur. Photos render correctly on real iOS devices regardless
⚠️
Page parameter type mismatch
matchedUserPhoto created as String type was greyed out in the Image Path picker. Fixed by changing parameter type from String to ImagePath
⚠️
Browser session caching for two-account testing
Safari Private window kept logging both windows into the same Firebase account. Solved by using two different browsers (Safari + Chrome) for genuinely isolated sessions
⚠️
Pass parameter dropdown overrides previous bindings
Switching the parameter dropdown loses the previous binding rather than adding a new one. Each parameter needs its own + Pass click — discovered the hard way after losing two bindings
⚠️
Authenticated User Photo URL is Firebase Auth, not Firestore
Initial left-photo binding pulled from Firebase Auth's photoURL field (empty) instead of the Firestore users doc photo_url. Solved by adding a backend query on GAMEON to fetch the current user's Firestore document
Phase Complete — Test Results
End-to-end test passed 18 May 2026 with two real Firebase accounts (Olcaraz + Djohn). Both users swiped right on each other. GAMEON page fired correctly on both sides with real names and real Imgur-hosted photos rendering in the split-screen layout. Mutual match detection, navigation, parameter passing, and dynamic UI all confirmed working.

Phase 7 — Build Report In Progress

Matches & Chat · 22 May–4 June 2026 (and ongoing)

Time Invested So Far
~26 hours, ~2h a night (nearly every night, 22 May–4 June · prep 22–24 May · AI scaffold + live test 25 May · row wiring 28 May · matches list + chat list 29 May · send, timestamps & header 29–31 May · GAMEON link + report user 1 June · reported-name + security planning 3 June · safety menu built & wired 4 June)

What's Shipped So Far

🛠️
9 binding fixes in the swipe chain
Discovered and fixed a deep bug across four action steps that was silently writing empty user IDs. Without it, mutual matches couldn't be detected at all
Mutual match verified live
Two real accounts on two browsers both swiped right. GAMEON fired on both sides simultaneously with correct names, photos, and no errors. Firestore confirmed a clean matches doc
🧭
Navigate-to-GAMEON parameter fixed
The matchedUserId page parameter had the same broken pattern as the swipe chain. Rebound to Document Reference ID — now GAMEON receives a stable, populated match ID
🤖
MatchesListPage AI-scaffolded
FlutterFlow Designer AI generated the full layout — title, subtitle, eight placeholder rows, reusable MatchRow component, empty state — from a single short prompt
🔍
Backend Query on matches
Page-level Firestore query with an OR filter: matches where user1Id == auth.uid OR user2Id == auth.uid. Only the current user's matches load — no client-side filtering
🔁
Dynamic row rendering
Eight static placeholder rows replaced with one MatchRow wrapped in a ListView's "Generate Children From Variable". Each row now corresponds to a real match document from Firestore
🔗
First parameter bound dynamically
MatchRow's name parameter is now bound to a field on the loop variable — confirming the entire data pipeline (query → list → row) is wired and live
🧹
Junk data cleared
Stale swipes and self-match docs from before the binding fix were wiped from Firestore. Clean slate for the rest of Phase 7 testing
👥
Matches list shows real partners
Each row resolves the other player in the match and shows their real name, photo, and level — pulled live from their profile. The matches list is functionally complete
💬
Chat messages render live
The chat screen now pulls each match's message history and renders one bubble per message, live from Firestore. Sending, timestamps, and bubble alignment are next

Key Challenges Encountered

⚠️
A field that should never be empty, sometimes was
Several action steps were pulling a user's uid from a Firestore field that wasn't always populated — silently producing empty values that broke the entire mutual-match flow. The fix was switching every reference to use the document's own reference ID, which is guaranteed to exist
⚠️
Two-account testing leaked sessions
Safari plus Safari Private shared the same auth session despite "Private" mode, making it impossible to test as two distinct users. Switching the second account to Firefox gave properly isolated sessions
⚠️
FlutterFlow's dynamic-children option is hidden
The setting that turns a static list into a dynamic one isn't on the widget by default — it appears only after wrapping the row inside a ListView. Worth knowing for every future list-based page
In Progress — Next Up
The core loop — discover → match → chat — is complete and demoable: the send button, timestamps, sent/received alignment, the chat header, match-row and GAMEON "Plan a Hit" navigation, and the first safety feature (report user) are all built and verified in two-account live tests. Next is the rest of the safety layer, built safest → fragile: a shared Report / Unmatch / Block menu, a report reason-picker, then a security-hardening pass (participant-only Firestore rules, App Check, auth hardening) folded in before Block so the block is rule-enforced, then unmatch, block, an account-deletion screen (Apple-mandatory before any App Store submission), and an 18+ age gate at signup. Realistic remaining time: ~9–15 focused hours.

Your Input Needed

The following items need a decision or action before LET can launch. None of these are technical — they sit in your lane. Each one is explained plainly below.

Question 1
Which tagline do you prefer?

LET needs a short line that sits under the logo — on the app, the website, and any marketing. We have five options. They range from action-focused to name-first. Which one feels right to you?

A LET Tennis — Find a game fast Current favourite
B LET — Find a game fast
C LET Tennis — Tennis partners, fast
D LET — Find your next game
E LET — Play more tennis
Question 2
Registering the LET trademark

Before LET grows any following, we should register the name and logo as a trademark through IP Australia (the government body that handles this). This protects us from someone else claiming the name later.

~10 months
Processing time
$250–330
AUD per class (gov fee)
Lodge now
Recommended timing

Because processing takes the better part of a year, it's worth lodging the application as soon as the name is confirmed. We'd recommend engaging a trademark attorney to handle the filing — typically $500–1,500 all-in for a straightforward application.

Question 3
Apple Developer account

To put LET on the App Store — even just for beta testing — we need to be enrolled in Apple's Developer Program. This is a straightforward annual subscription through Apple's website. The question is whether it sits under a company ABN or a personal name.

$149 AUD
Per year
developer.apple.com
Where to enrol

Decision needed: should this be registered under an existing company ABN, or do we need a new entity set up for LET first? This affects how revenue from the App Store is paid out.

Question 4
Which domain name should we lock in?

LET needs a permanent web address before launch — for the marketing site, the App Store listing (Apple requires a privacy policy URL and a support URL), and for sharing the app with players. lettennis.com is already taken, and we've ruled out the longer .com.au option. Five candidates below — short, brandable, all need to be checked for live availability on GoDaddy before purchase. The recommendation is option A, but happy to explore alternatives.

A lettennis.app Recommended
B let.tennis .tennis TLD
C hitup.app matches in-app "Plan a Hit" copy
D gameon.app matches GAMEON page
E letplay.app action-oriented
✓ Decided
Age rating — 18+

LET will launch as an 18+ app. Because the app involves strangers arranging to meet in person, 18+ is the cleanest path through Apple's review process and limits legal exposure at launch. Junior and teen players can be considered in a future version once moderation infrastructure is in place.