The Problem

"What is our niche? It cant just be a healthy social media app and be a bland interface, that is like eating raw kale, Its great for you but no one actually enjoys eating it. So then how do we make immi attractive to new users? They can't find friends on here as we don't recommend contacts so its purely going to be word-of-mouth."

- An early note from my sketchbook

The Problem

First we started with the key problems we were trying to solve within tech and social media as it exists today. Addictive patterns, doom scrolling, time spent in app. The founding team wanted a healthier alternative, something that reduced time spent and got away from addictive usage patterns.

The tension I identified early was that "healthier" is not a compelling enough reason for someone to actually use you. In a world where kale and injera exist, people still eat McDonald's because it's easy and it tastes good. Tech and consumer social are the same way. Endless scrolling of shortform media gives people the same kind of hit. So my job wasn't just making something good for people, it was making something people actually wanted to open, without falling back on the same manipulative patterns we were trying to get away from.

Design Debt

What I Inherited

When I first joined Immi they had already launched an MVP. The app had a basic one to many post feed and one on one replies, built on a React Native boilerplate. Our focus for the first sprint was fixing the design debt they'd built up before touching anything on the product side.

React Native Boilerplate

Technical Debt

Before touching the product experience, I audited the entire codebase for hardcoded hex values across all files and established a semantic token architecture for color and typography across light and dark mode.

const cardBackgroundColor = backgroundColor === '#151718' ? '#1E2022' : '#F5F5F7';

const dividerColor = backgroundColor === '#151718' ? '#2A2D30' : '#E5E5E7';

const accentColor = '#0a7ea4';

const dangerColor = '#FF3B30';

const cardBackgroundColor = backgroundColor === '#151718' ? '#1E2022' : '#F5F5F7';

const dividerColor = backgroundColor === '#151718' ? '#2A2D30' : '#E5E5E7';

const accentColor = '#0a7ea4';

const dangerColor = '#FF3B30';

Hardcoded Hex Values from Settings.tsx

Tokens

From there I built out new text stylings and button components to be used throughout the app, following the rules I'd set in our brand design guidelines. I built a multi-use button with 7 color variants, 3 sizes, all WCAG compliant, where all a dev had to do was change the onClick. I used semantic naming like "warning" and "danger" so the actual color decisions were already made for them.

Color Tokens in Figma
Button Color Variant Components
Light and Dark Mode Screenshots on IOS

Home Screen Redesign

Home Screen

After finishing the styling sprint, we moved to the next big thing: the home screen. I wanted an interface that promoted connection and healthy usage, felt original to us, and used a mental model people were already familiar with so adoption would be easy. The existing feed made the app feel like a watered down version of every other social app out there, not something built for real connection.

Figma Make

First things first we spun up a FigJam brainstorm and tried to come up with as many ideas as possible. From there I went into Figma Make and gave it our story and our design tokens, and had it generate playgrounds for each idea, five at a time. Some of the directions: Apple Watch style bubbles, a zen garden, constellations, a bookshelf. Using Figma Make let us as a team quickly iterate and test feasibility across a bunch of directions before wasting real design and engineering time on any one of them.

From everything we tested we landed on five directions worth actually prototyping: the Apple Watch bubbles, the stack of postcards, and shelves of books, cork/community board and a pinterest style feed.

Walking through my Figma Make
Narrowing Down Viable Directions
Postcard Stack & List View

Designing The Gestures Before the Look

Strip away any visual metaphor and there's only so many ways someone can engage with a post on mobile: tap, swipe, hold, double tap. I designed around those first, before any decorative layer, so the final mechanic would have clear gestures instead of interactions invented just to serve whatever visual idea we landed on.

Why Mail

Posts on Immi are sent to specific people, not published for anyone. I grounded that in a mental model everyone already has: sorting through physical mail. It requires no onboarding, carries real emotional weight, and implies what's in your hands was sent by someone thinking of you specifically.

The postcard format made all of this literal, it maps cleanly to the gesture vocabulary (swipe through the stack, tap to open), and it gives the feed the emotional register of receiving mail, not consuming content.

Stamps

I designed a custom stamp system users attach to each postcard before sending, expressive metadata that signals a post's tone or topic before the recipient opens it. Since posts disappear after 24 hours, this gave people enough context to decide whether to engage before committing, without adding friction to the creation flow.

Postcards
Stamps

Knowing Which Fights to Have

The next big thing we had to get past as a team was the founder's worry about doom scrolling and putting images on posts at all. Me and the engineers talked it through with him, the real problem with doom scrolling isn't the scrolling itself, it's that the scrolling never ends. Since our feed would only ever be posts other users actually sent you, that problem didn't apply here. I framed scrolling as just a means to an end, the same way you can only fit so many words on a page before you flip it, or so much art on one museum wall before you move to the next. As a first time Founding Design Partner I learned a lot here about navigating directly with a founder, when to stand up for the design and when to concede, and which hills were actually worth dying on

Vertical, and Draggable

I chose a vertical card orientation over a traditional horizontal postcard, forcing landscape on a portrait device breaks the native mobile interaction model for no real gain. Vertical maximized image space while keeping sender, title, and stamp legible without truncation.

To push the physicality further, I let users drag and reposition cards in the viewport, a small shift that turns scrolling through a feed into handling objects someone actually sent you.

Prototyping and Engineering

Prototyping and Engineering

Parameters

The first move was spinning up a Next.js prototype using Motion.dev to get the stack animation and swipe into the founder's hands as fast as possible. I knew once he could actually interact with it he'd be sold on it, and he was. While in Next.js I built the postcard to dynamically load data from our Supabase, and laid out the full parameter schema it would need whenever it got called:

type PostcardParams = {

imageUrl: string;

stampUrl: string;

postTitle: string;

senderName: string;

rotation: number;

scale: number;

zIndex: number;

offset: number;

isTop: boolean;

dropShadow: number;

};

Next.js Prototype

Defining these parameters upfront meant the component could be called consistently across both web and native surfaces, without requiring the engineer to make design decisions at the implementation layer. Visual variance between cards: randomized rotation, scale, and shadow was built into the parameter system rather than hardcoded, so the stack always read as a natural, physical pile rather than a uniform grid.

Next.js Swiping Prototype

Threading and Animation Architecture

This was the most technically demanding part of the native build, made harder by a version constraint: to stay aligned with the live app, I built on SDK 52, which meant the newest version of React Native Reanimated wasn't available to me. Rather than get the gesture and animation handling as a convenience from the library, I had to build the swipe mechanics and animation logic from scratch.

Since I was unable to use Motion.dev like I did for the Web prototype. I started as simply as possible: three colored View components labeled with numbers, isolating the stacking and repositioning logic before touching any real UI. Working iteratively with Claude to get the gesture math and animation timing right, I built up from that primitive harness to the real postcard component once the underlying mechanic actually worked.

Getting the Animations Correct

Stack - V1

My original approach used a sleight-of-hand sequence: animate the top card off screen, then update the array order on the JS thread to reposition it to the back of the stack once it left the viewport. Fast, repeated swipes exposed the real problem, since the reorder had to happen on the JS thread, I was always going to be stuck choosing between pausing the animation until the update completed, letting frames drop, or making the user wait for the current animation to finish before the next swipe registered. There was no version of that architecture that avoided the tradeoff; the constraint was structural, not a bug to patch

Sleight of Hand

Stack - V2

My second attempt used a dual-stack architecture: a visible stack in the viewport and a shadow stack rendering one card at a time off-screen, so the repositioning logic could run without blocking the animation thread. This resolved the threading conflict, but introduced a new problem: at some point, the last card in the stack had no shadow card behind it to animate from. That meant either breaking the illusion for that one card, or forcing the user to "throw away" their final post with no next card to replace it, an action they never actually asked for.

A Hidden Stack

Stack - V3

The final solution avoided the JS-thread dependency altogether. Rather than reordering cards mid-animation, swiped cards simply shrink in scale and fade in opacity as they translate down toward a persistent list-view toggle in the lower corner, making the destination of each card explicit to the user. I built this using Reanimated worklets, running the shrink, fade, and translate entirely on the UI thread rather than the JS thread, so the animation stayed smooth regardless of what else was happening in JS, which is what actually made the whole thing run smoothly, not just the architectural choice to stop reordering. This eliminated the threading conflict entirely, resolved the final-card problem without forcing an arbitrary discard, and addressed a separate concern the founder had raised: giving power users who wanted a specific post a way to navigate non-linearly.

The list-view button toggles between the stack and a condensed list showing title, sender, and stamp, with cards animating fluidly between full postcard size and list-item size to maintain spatial continuity across both states, also driven by worklets, so the transition holds 60fps whether or not the JS thread is busy.

Animation Worklets

Final Polish, Dynamic Backgrounds

Once that architecture was settled, I moved on to styling the postcards themselves while the animation sequencing and remaining edge cases were still being finished, refining the visual layer in parallel rather than waiting on the engineering to be airtight first.

The background treatment was one of the last decisions: a light gradient built from the three dominant colors extracted from each card's own image. Every postcard ends up with a visual atmosphere tied to its own content, rather than sitting on a generic static background, reinforcing that each card is a distinct, personal object rather than a templated post

Next.js Swiping Prototype

Web Experience and Zero-Friction Sharing

Web Experience and Zero-Friction Sharing

Web Experience

With the native app still in development, I built a parallel web experience on Next.js to get the postcard mechanic in front of real users as quickly as possible. Rather than requiring signup, I built a zero-friction postcard creation flow backed by a separate Supabase instance where users can create a fully custom postcard, image, message, stamp, and generate a shareable URL that expires after 48 hours.

The intent was twofold: validate the mechanic with real users outside our immediate network, and give the product a viral surface that could drive awareness without depending on app store distribution. Anyone who receives a postcard link sees the full experience in the browser and is prompted to download the app or send their own postcard, thus creating a natural acquisition loop.

I also built and maintain the Immi marketing site in Next.js, owning the full brand expression on web from copy through visual design through deployment.

Next.js Swiping Prototype

Outcomes

The postcard mechanic shipped on web and native. Real users sent real postcards to people they cared about. The team ultimately paused the project due to the structural retention challenges inherent to consumer social — not product quality, but the cold start problem. The web sharing experience, the marketing site, and the codebase remain live.

What I would do differently: establish more systematic user research touchpoints earlier in the home screen redesign process rather than relying primarily on founder and team feedback as a proxy for real user behavior, and document design directions more rigorously during exploration rather than reconstructing the rationale afterward.

Back to Top