/ home  ›  apps  ›  memberdex
MemberDex app icon

MemberDex

A white-label, multi-tenant member directory platform. Native iOS and Android apps, one admin console, one branded experience per organization. Built end-to-end — iOS, Android, web, backend, billing, ops.

One platform. Many communities.

MemberDex started in a treatment room. My PT, Ashley Katzenback (DPT, CMTPT), mentioned in passing that managing the business-networking groups she belongs to was harder than it ought to be — every member's contact info tangled into the same iPhone Contacts list as every plumber, every friend, every old colleague. Hard to find anyone in the group when she needed to. Hard to keep current as people came and went. Three days later I sent her a prototype of MemberDex v0.1. Off we went.

What grew from that conversation is a single white-label SaaS for the same kind of community problem at scale — business-networking groups, chambers of commerce, trade associations, HOAs, and country clubs. Members get a branded native app — iOS and Android — with their org's logo and color palette, a searchable directory, and an optional in-app news feed and community home page. Admins get a web console for member lifecycle, custom fields, billing, audit, engagement insights, and one-click backup. Each platform ships a single binary that picks the right org at runtime.

It's a small, sharp full-stack project: native Swift 6 and Kotlin 2.1 clients, React 19 admin SPA, Go backend with embedded SQLite, Stripe billing, Caddy + Hetzner ops. Built solo, end-to-end, and deliberately portable.

The Android client is now on Google Play — Kotlin 2.1 (K2) and Jetpack Compose with Material 3, talking to the same Go API over a certificate-pinned Retrofit/OkHttp stack. Same product, native idioms on each platform — hardware-backed biometric unlock, auth tokens sealed in the Android Keystore.

What it does.

🏛️

Multi-Tenant by Design

Row-level org isolation. One App Store binary serves every community. Super-admin can browse cross-org for support without leaving the app.

🎨

Per-Org Branding

Logo upload with live preview, accent palette, community page editing, category chips. The app feels native to each organization.

🧩

Custom Field Templates

Standard fields out of the box, or per-org custom field sets — chambers track different things than HOAs. Drag-and-drop reorder, bulk apply.

📥

Bulk Import & Invites

CSV import with drag-and-drop preview before commit. Invite tracking, last-login visibility, soft deactivation, tombstone messaging for removed users.

🔐

Security That Earns Trust

Phishing-resistant WebAuthn passkeys and enterprise SSO (OIDC + SAML) for the admin console, HIBP breach check on signup, per-account TOTP 2FA, JWT access + DB-backed refresh with atomic rotation, rate limiting, public-key cert pinning in release builds.

📊

Audit Trail

Login, invite, admin, backup, restore, and billing events recorded per-org. Searchable, exportable, retained.

💾

Backup & Restore

One-click archive download, atomic restore upload. Org admins control their own data.

💳

Stripe Billing

Tiered subscriptions, setup fees, invoice items, webhooks. Self-service plan selection. Billing-aware feature gating.

📡

Offline-Ready

Member rosters cached on device. Tap-to-call, tap-to-email, tap-to-map work without a connection. No tracking, no third-party SDKs.

📣

News Feed & Home Page

Optional in-app announcements with an unread badge, plus a community home page pinning links to meetings, events, and forms. Toggle on per org.

📈

Engagement Insights

See who's active and who's gone quiet, so admins can nurture a community instead of guessing. Retention email tooling built on the same signals.

🤝

Partner Contacts

Keep vendors and sponsors in the directory without counting them against the member ceiling. Shared team notes add private context on a member, visible to admins only.

How it's built.

Single-box production shape: one Hetzner VPS running Caddy as the TLS-terminating reverse proxy, the Go API (memberdexd) as a static binary, and SQLite (WAL mode) as the database. The admin SPA builds to static assets and is served same-origin behind Caddy — no CORS, no auth split-brain. The marketing site lives separately on Cloudflare Pages.

◆ Clients iOS app (Swift 6 · pinned) Android app (Kotlin · pinned) Browser (React 19 · same-origin) ↓ TLS ◆ Edge Caddy 2 (ACME / Let's Encrypt · TLS termination · reverse proxy) ◆ Application memberdexd (Go 1.25 · chi router · static binary) handlers → services → repos ◆ Data + Integrations SQLite (WAL) · Stripe (billing) · Resend (email)

Tech.

iOS Client

  • Swift 6 with strict concurrency
  • SwiftUI, MVVM
  • Native async/await, URLSession — no third-party HTTP libs
  • iOS 17+ deployment
  • XcodeGen (project.yml)
  • Cert pinning via APIPinningDelegate
  • Demo mode + super-admin org switching

Android Client

  • Kotlin 2.1 (K2 compiler)
  • Jetpack Compose + Material 3
  • minSdk 26 · targetSdk 35 · JDK 21
  • Retrofit + OkHttp + kotlinx-serialization
  • Hilt DI · Coroutines + StateFlow (MVVM)
  • OkHttp cert pinning · Keystore-sealed tokens
  • BiometricPrompt unlock · Coil 3 imagery
  • Multi-module Gradle (version catalog)

Admin SPA

  • React 19.2 + TypeScript 5.9
  • Vite 8 · Tailwind CSS 4
  • React Router 7 · React Query 5
  • Vitest 4 + Testing Library
  • Playwright end-to-end
  • Built static, served by Caddy (same-origin)

Backend

  • Go 1.25 + chi router
  • SQLite WAL, embedded migrations
  • golang-jwt, bcrypt, uuid
  • stripe-go/v82
  • HIBP k-anonymity password breach check
  • OpenAPI spec auto-generated
  • CGo-free single binary (~50MB)

Infra & Ops

  • Caddy 2 (auto-TLS via ACME)
  • Hetzner Cloud single-box VPS
  • Cloudflare DNS + Pages + Turnstile
  • Resend transactional email
  • systemd-managed daemon
  • Bash deploy + verify + backup scripts

CI / CD

  • GitHub Actions matrix:
  • — Go unit tests (Linux)
  • — Node verify + Playwright E2E
  • — iOS XCTest (macOS)
  • — Android assembleDebug + unit tests
  • — detekt · ktlint · spotless
  • Local verify_local.sh gate
  • Production deploy_production.sh with safety checks

Security & Hardening

  • Passkeys: WebAuthn/FIDO2 — discoverable creds, UV-required, phishing-resistant; clone-counter detection
  • SSO: OIDC + SAML 2.0 per-org against a verified domain; JIT admin provisioning, no escalation
  • Auth: per-account TOTP 2FA + brute-force lockout
  • Tokens: JWT parser pinned to HS256; atomic refresh-token rotation
  • iOS: public-key pinning via APIPinningDelegate
  • iOS: Keychain WhenPasscodeSetThisDeviceOnly
  • Android: OkHttp SPKI pin-set (fail-fast in release builds)
  • Android: AES-256/GCM tokens in Android Keystore (device-secure required)
  • Android: hardware auth-bound biometric key, invalidated on re-enrollment
  • HTTP: security headers, Origin guard, returnURL allowlist
  • Public forms: Cloudflare Turnstile + honeypot + 32 KiB body cap
  • Abuse: rate limits (per-IP / daily / dedup), disposable-email list, link-density + botnet-template heuristics
  • Proxy: trusted-proxy CIDRs for accurate XFF parsing behind Caddy
  • Memory: bounded rate-limiter map size under attack
  • Signup: HIBP k-anonymity password breach check, 12-char minimum
  • CSP on marketing site; audit log on every privileged action
  • Privacy + Terms + Subprocessors page; transparent data handling

What's been shipping.

Passwordless passkey sign-in + enterprise SSO

Two authentication tracks landed for the admin console. WebAuthn/FIDO2 passkeys give phishing-resistant, passwordless and usernameless sign-in — discoverable credentials with user-verification required, so Face ID / Touch ID / Windows Hello / a hardware key just works and the authenticator picks the account. The private key never leaves the device's secure element; each stored credential carries a signature counter, and a counter regression (a cloned authenticator's tell) is refused and audited. The ceremony lives in an isolated Go package so only this product links the WebAuthn dependency — the same seam used for SSO. Enterprise SSO covers OIDC (Okta, Entra, Google Workspace, Auth0) and SAML 2.0, configured per organization against a verified email domain with just-in-time admin provisioning that can never escalate a role. Both are strictly additive — password and TOTP stay live, so a lost device or a misconfigured IdP never locks anyone out.

WebAuthn / FIDO2 OIDC + SAML Phishing-resistant Zero-trust auth

Self-service community signup

A founder fills a form and the backend provisions a pending org plus an admin account with a set-password email — they can brand the space and import members while pending, and a single operator approval unlocks member invites, billing, and the trial. Closed the last fully-manual step in onboarding.

Engagement layer — news feed, home page, insights

Per-org toggle-on features: an in-app news feed with unread badges, a pinned community home page, and engagement insights that surface active vs. dormant members — with retention-email tooling built on the same signals.

Android client shipped to Google Play

Native Kotlin 2.1 / Jetpack Compose client (Material 3, multi-module Gradle, version catalog) against the same Go API — certificate-pinned Retrofit/OkHttp transport, AES-256/GCM auth tokens sealed in the Android Keystore, hardware-bound biometric unlock, and TOTP 2FA. Built to parity with the iOS app, native idioms throughout.

App Store 1.0 hardening

iOS appearance setting (Light/Dark/System), splash scheme polish, cert pinning verification, App Store rejection log management. Pre-1.0 launch maturity work.

Security audit + hardening pass

Systematic hardening across auth, transport, and public surface. Per-account TOTP 2FA with brute-force lockout. JWT parser pinned to HS256 (no algorithm confusion). iOS public-key pinning via APIPinningDelegate + Keychain tightened to WhenPasscodeSetThisDeviceOnly. HTTP security headers, Origin guard, returnURL allowlist. Public inbound forms get Cloudflare Turnstile + honeypot + 32 KiB body cap + per-IP / daily / dedup rate limits + disposable-email + link-density + botnet-template heuristics — backed by trusted-proxy CIDRs for accurate XFF, and a bounded rate-limiter map so attackers can't grow memory. Audit log on every privileged action. Privacy, Terms, and Subprocessors page rewritten for current best practices.

Drag-and-drop CSV import

Admin can drop a CSV, see a row-by-row preview, reorder/map fields, then commit. Same drag-and-drop pattern used for field reordering.

Super-admin billing console

Effective-price detail, plan tier overrides, setup-fee management. Self-service plan selection from the org admin side.

Logo lifecycle & trash

Soft-delete pattern for org logos with restore/permanent-delete affordances. Live brand preview during upload.

Pre-1.0 hardening Security Admin UX Billing