Where it came from
I've always had this oddly specific problem.
I'd meet someone, have a real conversation, feel genuinely connected, and then weeks later realize I'd forgotten the details that made it meaningful. The context. The thing they mentioned about their family. The reason we clicked. That feeling of forgetting someone who actually mattered stayed with me.
We have tools for almost everything -- calendars for dates, notes for ideas, task managers for work -- but for the people who shape our lives, we mostly just try to remember, and quietly feel bad when we don't.
The existing options either felt too corporate (a CRM is not the right mental model for your personal life) or too shallow (a simple contacts list doesn't capture anything). I didn't want a pipeline. I wanted something simpler and more honest than that.
So I built the first version of KnowThem for myself. It was tiny -- just a place to save people I'd met, write notes about them, and search through later. No grand vision. Just a small thing that solved a real personal problem.
As I used it, I noticed something. Memory isn't just names. It's continuity: conversations, follow-ups, promises made, small details that show someone you were actually paying attention. The tool grew slowly and honestly, in response to real friction I kept running into.
What it does
KnowThem is a personal relationship manager. You add the people in your life -- friends, mentors, colleagues, family -- with notes, tags, relation type, photo, phone, email. Then you log interactions: a call, a meeting, a coffee, a message. Set reminders for birthdays, check-ins, follow-ups. If you connect Google Calendar, those reminders create events there automatically.
The relationship strength piece is one of the more useful features: every person gets one of five statuses (active, warm, inactive, cold, new) based on how recently you've interacted with them. It's simple math, but it makes the drift visible. You can see at a glance which connections have gone quiet.
You can also import directly from Google Contacts so you don't have to type everything from scratch.
The design philosophy
As the product grew, one question kept coming back: does this still feel human?
The interface is soft and minimal. The color palette is a warm green -- grounded and calm, not loud or urgent. There's no pipeline language, no "leads" or "stages." Just people and the moments between you. The goal was something that feels worth opening, not something that feels like maintaining a spreadsheet.
It's still being shaped. I'm learning from real use, adjusting things that don't quite work, and trying to move toward something that actually reduces the effort of staying connected rather than adding to it.
Architecture
The backend is FastAPI deployed on Vercel's Python serverless runtime. Supabase provides the database (Postgres) and authentication. Profile photos are stored in Cloudflare R2, which is S3-compatible and has no egress cost.
Browser (Jinja2-rendered HTML + Vanilla JS)
-- fetch() with JWT --> FastAPI (Vercel serverless)
|
+----------------+-----------------+
| | |
Supabase DB Supabase Auth Cloudflare R2
|
Google Calendar API (for reminders)
Google People API (for contacts import)
HTML pages are rendered server-side by FastAPI using Jinja2 templates. Data is fetched asynchronously from API endpoints via JavaScript. Auth state lives in localStorage as a Supabase JWT.
A few things worth understanding
Per-request auth on the database client: Every API call that touches the database creates a Supabase client with the user's JWT attached. This is what activates Supabase's Row Level Security -- the database enforces that each user only sees their own data. Without this, queries would return everyone's data.
Soft delete: When you "delete" a person, they move to a Trash view rather than being immediately removed. You can restore or permanently delete from there.
Relationship strength algorithm:
No interactions ever → "new" Last interaction within 7 days → "active" Last interaction within 30 days → "warm" Last interaction within 90 days → "inactive" Over 90 days since last contact → "cold"
This runs as a single bulk operation: fetch all people, fetch all interactions, compute status per person in memory. Two database queries total regardless of network size.
Google Calendar sync: When you save a reminder, it tries to create a Google Calendar event. If the Calendar API fails, the reminder still saves locally. Calendar sync is best-effort, never blocking.
Building it
I designed the data model, the API structure, and the overall architecture, then used AI-assisted development to accelerate the implementation. For a project with this many moving parts -- auth flows, Google OAuth integrations, image pipelines, a multi-page frontend -- moving fast on implementation once the design was clear made a real difference.
The Google Calendar and Contacts integrations involved OAuth flows that took time to get right. Understanding what a refresh token is, how token exchange works, and where to store credentials safely were things I had to work through carefully.
Tech stack
| Layer | Technology |
|---|---|
| Backend | FastAPI, Python |
| Templates | Jinja2 |
| Validation | Pydantic v2 |
| Database and auth | Supabase (Postgres + Auth) |
| Image storage | Cloudflare R2 |
| Image processing | Pillow |
| Google Calendar | google-api-python-client |
| Google Contacts | httpx (direct REST) |
| Frontend | Vanilla JS, Vanilla CSS |
| Icons | Lucide |
| Hosting | Vercel |