You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Supabase supplies four separate things — Postgres, Auth/SSO, Storage, and (indirectly) transactional email via a Resend SMTP relay configured inside Supabase Auth. Exiting means choosing four replacements, and the choices are coupled: the auth provider determines whether user UUIDs survive, which determines whether Project.userId needs a backfill, which determines the order of tasks 5 and 7. This is a decision task — its deliverable is a recorded decision in docs/decisions.md, not code. The recommendation below is researched but must be confirmed by the human at Gate 1, because it commits the project to a specific vendor set for the foreseeable future.
Researched recommendation, with the disqualifying facts that drove it:
Concern
Recommendation
Why, and what was ruled out
Postgres
Neon free plan
Neon's free plan has no inactivity-based pause, archival, or deletion — the only idle behaviour is compute scale-to-zero after 5 minutes, which resumes automatically on the next connection. That is precisely the failure being escaped. Limits: 0.5 GB storage/project, 100 CU-hours/month, 5 GB transfer/month; exceeding any of these suspends compute until the next monthly window (data is never deleted). Render Postgres is disqualified — free databases expire 30 days after creation and are deleted after a 14-day grace period. Aiven pauses inactive databases. Xata's managed free database is removed after a set period.
Storage
Cloudflare R2
10 GB storage, 1M Class A + 10M Class B operations/month, $0 egress, permanently free, no idle expiry. S3-compatible, so the swap is an @aws-sdk/client-s3 drop-in behind the three functions in storageService.ts. Preserves the one-Solar-API-call-per-location cache constraint exactly.
Auth
Better Auth, self-hosted in the Express backend, tables in the same Neon database via the Prisma adapter
The decisive property is user-ID preservation: Better Auth's user table takes a String id, so the rescued Supabase UUIDs can be inserted verbatim and every Project.userId FK survives with zero backfill. It supports email+password with requireEmailVerification, Google OAuth via socialProviders, and a user-supplied sendVerificationEmail callback that wires straight to Resend. No MAU cap, no vendor that can change its free tier, no idle pause. Alternative worth considering: Clerk (free to 50k MRU as of Feb 2026, far less engineering) — but Clerk mints its own user_... ids, forcing either a mapping table or a Project.userId migration, and re-introduces exactly the "someone else's free tier" dependency this phase exists to remove.
Email
Resend, called directly from the backend
Already the underlying sender; today it is merely relayed through Supabase Auth's SMTP config. solarsim.tech is already verified in Resend with DKIM, SPF, and an MX bounce route (per RUNBOOK.md), and RESEND_API_KEY is already in .env.example. Removing the Supabase relay is therefore a simplification, not a new integration — no DNS work.
Scope:docs/decisions.md, docs/trd.md
Checklist
Re-verify each provider's current free-tier terms at decision time rather than trusting this table — free-tier terms move fast and this plan was written 2026-07-29
Confirm the Neon free-plan headroom against real numbers from the task 1 rescue: total public schema size must fit 0.5 GB (GeoTIFFs live in object storage, not Postgres, so this should be comfortable — confirm, don't assume)
Compute the CU-hour budget: Neon free is 100 CU-hours/month and free-plan compute floors at 0.25 CU, giving roughly 400 hours of active compute. Record the consequence explicitly: a keep-alive pinger that touches the database will hold compute awake ~730 h/month ≈ 180 CU-hours and suspend the project. This constrains task 10's cold-start mitigation.
Confirm the total rescued storage-object footprint fits R2's 10 GB free tier
Spike Better Auth for one hour against a throwaway Neon database: insert a user row with a hand-chosen UUID, sign in with email+password, and confirm the id round-trips into the session — this is the single assumption the whole auth plan rests on
Decide and record: if the spike fails, fall back to Clerk and accept a Project.userId backfill migration as an added task
Append one line per settled choice to docs/decisions.md (four lines: Postgres, storage, auth, email)
Update the infrastructure section of docs/trd.md to name the new providers — docs/trd.md is canonical for architecture and must not be left describing Supabase
verify: docs/decisions.md contains four dated one-line entries, each with a rationale clause
verify: the Better Auth spike's custom-UUID result is pasted into the GitHub issue as evidence, not asserted
Priority: Critical · Phase 12 task 4 of the maintainer's migration plan.
Supabase supplies four separate things — Postgres, Auth/SSO, Storage, and (indirectly) transactional email via a Resend SMTP relay configured inside Supabase Auth. Exiting means choosing four replacements, and the choices are coupled: the auth provider determines whether user UUIDs survive, which determines whether
Project.userIdneeds a backfill, which determines the order of tasks 5 and 7. This is a decision task — its deliverable is a recorded decision indocs/decisions.md, not code. The recommendation below is researched but must be confirmed by the human at Gate 1, because it commits the project to a specific vendor set for the foreseeable future.Researched recommendation, with the disqualifying facts that drove it:
@aws-sdk/client-s3drop-in behind the three functions instorageService.ts. Preserves the one-Solar-API-call-per-location cache constraint exactly.Stringid, so the rescued Supabase UUIDs can be inserted verbatim and everyProject.userIdFK survives with zero backfill. It supports email+password withrequireEmailVerification, Google OAuth viasocialProviders, and a user-suppliedsendVerificationEmailcallback that wires straight to Resend. No MAU cap, no vendor that can change its free tier, no idle pause. Alternative worth considering: Clerk (free to 50k MRU as of Feb 2026, far less engineering) — but Clerk mints its ownuser_...ids, forcing either a mapping table or aProject.userIdmigration, and re-introduces exactly the "someone else's free tier" dependency this phase exists to remove.solarsim.techis already verified in Resend with DKIM, SPF, and an MX bounce route (perRUNBOOK.md), andRESEND_API_KEYis already in.env.example. Removing the Supabase relay is therefore a simplification, not a new integration — no DNS work.Scope:
docs/decisions.md,docs/trd.mdChecklist
publicschema size must fit 0.5 GB (GeoTIFFs live in object storage, not Postgres, so this should be comfortable — confirm, don't assume)Project.userIdbackfill migration as an added taskdocs/decisions.md(four lines: Postgres, storage, auth, email)docs/trd.mdto name the new providers —docs/trd.mdis canonical for architecture and must not be left describing Supabasedocs/decisions.mdcontains four dated one-line entries, each with a rationale clausePriority: Critical · Phase 12 task 4 of the maintainer's migration plan.