Architecture · Visual Story

WorkOS is the wrong shape.

Here's the right one — in pictures.

§ 01 — The Problem

A stranger gets an email from "Adeel's Team."

§ 02 — Today

Everyone shares one brain.

WorkOS · Old
flowchart TB
  classDef user fill:#fbe9e9,stroke:#9b2226,stroke-width:2px,color:#1a2942,font-weight:600
  classDef site fill:#ffffff,stroke:#1a2942,stroke-width:2px,color:#1a2942,font-weight:600
  classDef workos fill:#fef0e6,stroke:#b85c2e,stroke-width:3px,color:#1a2942,font-weight:700
  classDef email fill:#fdf6e0,stroke:#a87a1f,stroke-width:2px,color:#1a2942,font-weight:600
  classDef bad fill:#9b2226,stroke:#9b2226,stroke-width:2px,color:#ffffff,font-weight:700

  U1["👤 User A"]:::user
  U2["👤 User B"]:::user
  U3["👤 User C"]:::user

  S1["sitzio.de"]:::site
  S2["fitsite.com"]:::site
  S3["...23 more"]:::site

  W["⚠ ONE WorkOS Project
──────────
👥 ONE shared user pool
📛 ONE brand for everyone
✉ ONE sender domain"]:::workos E["✉ From: Adeel's Team"]:::email X1["⚠ Same email = same user
across every site"]:::bad X2["⚠ Wrong brand
in every inbox"]:::bad U1 --> S1 U2 --> S2 U3 --> S3 S1 --> W S2 --> W S3 --> W W ==> E E -.-> U1 E -.-> U2 E -.-> U3 W -.- X1 E -.- X2
becomes
§ 03 — The Fix

Each site gets its own everything.

DIY · Postgres + Better-Auth + Resend
flowchart TB
  classDef user fill:#e8efe1,stroke:#5d6e3a,stroke-width:2px,color:#1a2942,font-weight:600
  classDef site fill:#ffffff,stroke:#1a2942,stroke-width:2px,color:#1a2942,font-weight:700
  classDef db fill:#dcebed,stroke:#265e63,stroke-width:2px,color:#1a2942,font-weight:600
  classDef auth fill:#fdf6e0,stroke:#a87a1f,stroke-width:2px,color:#1a2942,font-weight:600
  classDef email fill:#e8efe1,stroke:#5d6e3a,stroke-width:2px,color:#1a2942,font-weight:600
  classDef good fill:#5d6e3a,stroke:#5d6e3a,stroke-width:2px,color:#ffffff,font-weight:700

  U1["👤 User A"]:::user
  U2["👤 User B"]:::user
  U3["👤 User C"]:::user

  subgraph SITE1 ["🏛 sitzio.de"]
    direction TB
    S1["TanStack Start"]:::site
    A1["Better-Auth"]:::auth
    D1[("📦 sitzio_db")]:::db
  end

  subgraph SITE2 ["💪 fitsite.com"]
    direction TB
    S2["TanStack Start"]:::site
    A2["Better-Auth"]:::auth
    D2[("📦 fitsite_db")]:::db
  end

  subgraph SITE3 ["✨ niche-3.com"]
    direction TB
    S3["TanStack Start"]:::site
    A3["Better-Auth"]:::auth
    D3[("📦 niche3_db")]:::db
  end

  R["Resend
shared API key
multi-domain"]:::email E1["✉ noreply@sitzio.de"]:::email E2["✉ noreply@fitsite.com"]:::email E3["✉ noreply@niche-3.com"]:::email G1["✓ Different DB
= different user"]:::good G2["✓ Right brand
in every inbox"]:::good U1 --> S1 U2 --> S2 U3 --> S3 A1 --> R A2 --> R A3 --> R R --> E1 R --> E2 R --> E3 E1 -.-> U1 E2 -.-> U2 E3 -.-> U3 D1 -.- G1 E1 -.- G2
§ 04 — The Stack

Three pieces. All yours.

Storage
Postgres
Auth Flows
Better-Auth
Email
Resend
§ 05 — The Numbers

What the swap buys you.

15sec
Per-site setup
(was 5 min clicking)
$0/mo
First 5 sites
Resend free tier
3
DB tables
that's the whole thing
0
Cross-site
identity leaks
§ 06 — Side by Side

Where the shape diverges.

Aspect
WorkOS · Old
DIY · New
User isolation
Shared pool, same email = same user everywhere
Separate DB per site, leak is impossible
Per-site brand
One brand for the whole project
noreply@its-own.com per site
New site setup
Manual dashboard clicks, capped per account
One shell command, fully scripted
Vendor lock-in
User data lives in WorkOS
Standard SQL on your own box
Cost @ 5 sites
~
Free tier until limits, then scales
$0 — Coolify + Resend free
Auth UI theming
Light theming only, hosted UI
Total — it's your Next.js app
SSO / SAML / SCIM
Built in (you don't need it)
~
Add later if ever needed
§ 07 — Bootstrap a New Site

One command. No clicks.

Fully scripted
flowchart LR
  classDef start fill:#1a2942,stroke:#1a2942,stroke-width:2px,color:#ffffff,font-weight:700
  classDef step fill:#ffffff,stroke:#1a2942,stroke-width:2px,color:#1a2942,font-weight:600
  classDef new fill:#e8efe1,stroke:#5d6e3a,stroke-width:2px,color:#1a2942,font-weight:600
  classDef done fill:#5d6e3a,stroke:#5d6e3a,stroke-width:2px,color:#ffffff,font-weight:700

  Start(["▶ ./bootstrap-site.sh"]):::start

  D1["1. Buy domain"]:::step
  D2["2. Cloudflare DNS"]:::step
  D3["3. Create DB"]:::step
  D4["4. Auth migration"]:::new
  D5["5. Deploy"]:::step
  D6["6. Verify domain
in Resend"]:::new D7["7. DKIM records
to Cloudflare"]:::step Done(["✓ Live
noreply@newniche.com"]):::done Start --> D1 --> D2 --> D3 --> D4 --> D5 --> D6 --> D7 --> Done
§ 08 — Tradeoffs

Keep, drop, gain.

=

Keep

  • Coolify infra
  • DomainOps + DevOps agents
  • Auth agent (rename only)
  • Bootstrap pipeline shape
  • Cloudflare DNS + Pages

Drop

  • WorkOS entirely
  • "Adeel's Team" sender
  • Manual provisioning
  • Environment caps
  • Shared user pool
+

Gain

  • Per-site sender domains
  • Real user isolation
  • Scripted bootstrap
  • Zero vendor lock-in
  • $0 through 5 sites
Stop fitting B2B identity onto a B2C network.
Three small pieces. All yours. Ship it.