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
isProflag.
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
.envbut 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
- amazon-web-services
- android
- android-studio
- aws-lamda
- cloudinary
- figma
- jetpackcompose
- mapbox
- mongodb
- node.js
- onesignal
- revenuecatsdk
- upstash

Log in or sign up for Devpost to join the conversation.