Inspiration

Doing things is easier with company, but finding someone free right now is awkward. You post in a group chat, wait, and nobody replies. Generic “meet new people” apps don’t help, because the goal is a football game, a run, or a coffee in the next hour — not open-ended networking.

Juttela is built around one simple loop: post what you want to do right now, and instantly see people nearby who want the same thing. The activity is the icebreaker, which makes it far less awkward than a blank chat with a stranger.

What it does

Juttela connects nearby people through shared activities, in real time.

  • Find people nearby: pick an activity (football, running, yoga, coffee, and more) and see the closest active people within 5 km, sorted by distance.
  • Request, accept, chat: send a request, and if they accept, a chat opens for both of you.
  • Profiles you can trust: photo, age, gender, and a short bio, plus a rating summary, so you know who you’re meeting.
  • Push notifications: you’re notified when someone requests you, when your request is accepted, when a message arrives, and on meetup arrival.
  • Juttela Pro: a subscription for smart matches, unlimited chats, unlimited requests, and unlimited nearby users.

The app is designed for real-world meetups, so a few safety choices are built in:

  • Other users see only approximate distance, never your exact location.
  • Requests expire, so stale “right now” plans disappear.
  • Ratings are restricted to people you’ve actually connected with.

How we built it

Juttela is a native Android app in Jetpack Compose with a serverless backend.

  • Matching engine: Upstash Redis geospatial sets, one per activity. Each active user also has an expiry timestamp in a sorted set, so a window of $t_{\text{expiry}} = t_{\text{now}} + 1800\text{ s}$ keeps only genuinely active people discoverable.
  • API: Node.js on AWS Lambda behind API Gateway.
  • Persistent data: MongoDB for accounts, requests, connections, messages, profiles, and ratings. Redis holds only ephemeral “who’s here right now” data.
  • Cleanup: an AWS EventBridge schedule runs sweep functions every 5 minutes to remove expired users and stale pending requests.
  • Images: profile photos upload directly from the app to Cloudinary, so image bytes never pass through Lambda.
  • Engagement: OneSignal for push notifications, plus a Journey that welcomes new users and nudges them to complete their profile.
  • Monetization: RevenueCat for the Juttela Pro paywall, purchase flow, and entitlement checks. The app asks RevenueCat for the live entitlement status rather than storing its own isPro flag.

Challenges we ran into

  • Redis can’t expire one member of a geo set. Presence and location are separate structures, so I built an expiry sorted set, lazy cleanup during searches, and a scheduled sweep to catch everyone else.
  • Lambda doesn’t keep running after it responds. My “fire-and-forget” push notification silently never sent until I awaited it before returning.
  • A missing config block cost me hours. I chased device and channel theories before CloudWatch showed the real cause: my OneSignal keys existed in my local .env but were never deployed to Lambda.
  • Serverless can’t hold WebSockets, so chat uses lightweight polling instead of true push.
  • Toolchain crashes: a Kotlin compiler crash from a dependency version mismatch, and two separate Android navigation graphs that made a chat route “not found.”
  • The cold-start problem: a hyperlocal app is empty until people are nearby. I replaced hardcoded “people nearby” numbers with live counts so nothing on screen is fake.

Accomplishments that we're proud of

  • A full loop live on Google Play: find, request, accept, chat, and get notified — tested across real devices.
  • A cleanly separated data design, with fast, disposable state in Redis and durable state in MongoDB.
  • A complete monetization flow, from paywall to purchase to entitlement, with a celebration animation on success.
  • I changed my pricing model early. I dropped a pay-per-match idea after realizing users would feel cheated when a match didn’t lead to a meetup, and moved to a subscription that sells access, not outcomes.

What we learned

  • Trace failures back to their source. Almost every bug was a boring missing import, route, or config value — not the exotic problem I suspected first.
  • Ask the source of truth. RevenueCat knows subscription status better than my own code can.
  • Pick the simplest architecture that solves today’s problem. I nearly built regional Redis sharding for scale I don’t have.
  • Design trust into a stranger-meeting app from the start, not after launch.

What's next for Juttela

  • Live location sharing with a trusted contact, so a meetup is never fully off the grid.
  • Report and block tools, and more trust signals on profiles.
  • Real-time chat over WebSockets as usage grows.
  • Building dense local clusters of users, since the product gets better with every person nearby.
  • More automated OneSignal Journeys, and iOS.

Built With

Share this project:

Updates

Submission history