Contacts CRUD
Create, edit, delete, favorite, and browse contacts with multi-phone JSON fields.
NeedOwners need one reliable book, not scattered notes.
ValueSingle source of truth for important numbers.

ProjectsCase StudySaaS Product2026
Case Study
A security-first multi-tenant contact vault for families and small businesses—owner CRUD, view-only members, and passworded guest share links.
A security-first, multi-tenant contact vault where families and small businesses share numbers without giving away edit access.
Apna Contacts is a private shared phone book built on Next.js and Supabase. Owners manage one or more vaults; invited members stay view-only; guests open a passworded share link—no account required—to search, call, and WhatsApp.
Apna Contacts solves a simple, painful problem: important numbers live in one person’s phone, while family and staff keep asking for the electrician, the doctor, or the shop supplier in group chats.
The product is a private vault—not a public social directory. Owners create named workspaces (home, shop, family), invite members as view-only, and issue guest share links with optional passwords and expiry.
The engineering story is defense in depth: Server Actions enforce roles, Postgres RLS isolates tenants, and guests never touch tables through the Data API—only SECURITY DEFINER RPCs after a signed session cookie.
The build evolved from a single-owner family book into a multi-tenant SaaS with Google OAuth, CSV import (including Google Contacts), CSP nonces, and privacy-aware SEO that keeps private routes out of indexes.
Households and shops store numbers in personal phones or shared Google accounts, then forward screenshots, CSVs, or full account access whenever someone needs a lookup.
Contact books hold trusted personal and business relationships. The product only works if sharing is easy for guests and impossible to abuse—view-only by default, tenant isolation by design.
Apna Contacts keeps one source of truth for numbers and three clear access modes:
Full CRUD on contacts, categories, favorites, CSV import, invites, guest links, and workspace settings across multiple named vaults.
Authenticated view-only access to a single vault—search and open contacts without create, edit, or delete rights.
Password-optional, expiring links at /g/{token}. Guests get a signed cookie session and a mobile-first read-only contact UI—no signup.
All / Favorites / Recent tabs, multi-phone contacts, call / WhatsApp / copy actions, and category badges inspired by familiar phone contact apps.
Dual authorization (app roles + RLS), HMAC guest cookies, rate-limited share attempts, CSP with nonces, and noindex on private routes.
Families and shops keep one trusted book, share lookup access without edit rights, and separate home vs work vaults—while guests never become members and never gain table-level API access.
Create, edit, delete, favorite, and browse contacts with multi-phone JSON fields.
NeedOwners need one reliable book, not scattered notes.
ValueSingle source of truth for important numbers.
Search by name, phone, or category; filter All / Favorites / Recent.
NeedLookups happen on mobile, under time pressure.
ValueFaster find → call / WhatsApp.
Import standard and Google Contacts CSV with preview and validation.
NeedExisting phone books should not be retyped.
ValueLow-friction migration into the vault.
Multiple workspaces per account (e.g. Home, Shop) with active-workspace context.
NeedHome and business contacts should not mix.
ValueClean separation without multiple apps.
Email invite tokens, join flow, revoke, and member remove—members stay view-only.
NeedTrusted family needs access without owner powers.
ValueShared access without shared passwords for members.
Totals, favorites, weekly adds, category breakdown, and recent updates.
NeedOwners need a quick health check of the vault.
ValueClear overview before diving into contacts.
Create, list, and revoke guest links with optional password and expiry.
NeedRelatives should not need accounts for a quick lookup.
ValueFrictionless read access under owner control.
HMAC-signed HTTP-only cookie after RPC session create; middleware validates signature then DB session.
NeedAnonymous access must still be authenticatable and short-lived.
ValueGuest UX without opening the Data API to anon.
Read-only list and detail under /{slug}/guest/contacts with call / WhatsApp / copy.
NeedGuests need the same lookup actions as members.
ValueUseful sharing, not a dead view-only dump.
Supabase Auth with signup bootstrap that creates a personal workspace via DB trigger.
NeedOwners need a fast, familiar sign-in path.
ValueLower setup friction for the person who owns the vault.
Profile, password change, and delete-account confirmation.
NeedPersonal data products need clear exit and security hygiene.
ValueOwner trust and compliance-minded UX.
Signs up or signs in (email / Google)
Personal workspace bootstrapped automatically
Adds contacts or imports CSV
Optionally creates a second vault (e.g. Shop)
Invites family members as view-only
Creates a guest share link for relatives
Accepts invite and signs in
Opens the assigned vault contacts
Searches or browses Favorites / Recent
Calls, WhatsApps, or copies a number
Cannot create, edit, or delete contacts
Opens /g/{token}
Enters optional share password
Receives signed guest session cookie
Browses read-only contacts for that vault
Calls or WhatsApps without creating an account
App roles (owner / member / guest) gate Server Actions; Postgres RLS isolates rows by workspace and restricts writes to owners.
Business valueA bug in the UI cannot casually bypass tenant or write rules.
Anonymous clients never get table grants. Guest list/get flows run through SECURITY DEFINER RPCs with narrow return columns.
Business valueShare links stay useful without exposing the workspace schema.
Guest sessions are signed with GUEST_SESSION_SECRET, validated in middleware, then checked against DB session validity.
Business valueShort-lived, tamper-resistant guest access without full auth accounts.
Migrations document the path from single-owner share password to workspaces, invites, member view-only, and authz hardening waves.
Business valueShows production discipline: ship, secure, then scale the tenancy model.
Per-request CSP nonces, strict security headers, robots/noindex on private routes, and llms.txt guidance for crawlers.
Business valuePrivate contact data stays out of indexes and training scrapes by default.
CSV parser handles header aliases, multi-phone rows, system labels, and starred → favorite mapping with preview before insert.
Business valueOwners migrate real phone books without manual cleanup first.
| Challenge | Solution |
|---|---|
| Family needed easy lookup access without giving everyone edit rights or a full Google account. | Three-tier access: owner CRUD, authenticated view-only members, and passworded guest share links. |
| Anonymous guest access is a classic leak surface if guests can query tables directly. | No anon table grants—session RPCs plus HMAC cookies and rate-limited password attempts. |
| Home and shop contacts mixed when everything lived in one personal phone book. | Named multi-tenant workspaces with RLS isolation and an active-workspace switcher. |
| Early single-owner share-password design did not scale to invites and multiple vaults. | Incremental migrations to workspaces, invitations, guest_share_links, and repeated security hardening. |
| Private product routes must not become public SEO or AI-crawler fodder. | noindex on app routes, robots disallow for contacts/guest/invite paths, and privacy-aware llms.txt. |
Recommended capture order for the case-study gallery.
For personal data products, the access model is the product—owners, members, and guests need different journeys, not one shared login.
App-layer checks and Postgres RLS should say the same thing; either alone is not enough for multi-tenant contact data.
Guest flows stay safer when anonymous users never get table grants and only call narrowly scoped RPCs.
Iterative hardening migrations (token secrecy, invite email binding, rate keys) are part of shipping—not a one-time checklist.
| Priority | Improvement | Business value |
|---|---|---|
| High | Invite email delivery (Resend) so owners do not rely only on copyable links | Faster member onboarding with less manual sharing. |
| High | Fine-grained roles and selective contact sharing beyond owner vs view-only | Better fit for shops where some staff need limited edits. |
| Medium | Workspace switcher and settings UX polish for multi-vault owners | Clearer day-to-day vault management. |
| Medium | Contact activity / recent-call hints for guests and members | Faster repeat lookups for frequently used numbers. |
| Low | Optional WhatsApp deep-link templates and label presets per category | Slightly faster outreach from the detail screen. |
Apna Contacts is a full-stack case study in private data sharing—workspaces, RLS, guest sessions, and a mobile-first vault UX. If you are building something where permissions are the product, let’s talk.
Start a conversationNotes on how guest access, tenancy, and authorization stay aligned across the Next.js app and Supabase.
Owner, member, and guest are different products in one codebase. Write paths require owner; members and guests only read. That rule is enforced in role helpers and again in RLS policies.
Guest share passwords protect the link—not the workspace membership model. Guests never become members through a share URL.
Every contact, category, invite, and share link is scoped by workspace_id. Signup bootstraps a personal workspace via a database trigger so owners land in a usable vault immediately.
The workspace slug in guest URLs is branding only—the active guest workspace always comes from the validated session, never from a client-trusted path segment alone.
The SQL history is part of the product story: initial contacts schema, production owner RLS, guest sessions, multi-tenant workspaces, member view-only, then waves that hide share tokens, bind invites to auth emails, and harden rate limiting.
That arc is deliberate—ship a useful vault, then harden the surfaces that appear once real sharing begins.
Full Stack Product Engineer
Personal product / SaaS build
2026
Solo build
Next Project