# ============================================ # PROPERTY MANAGEMENT NETWORK — Environment Variables # ============================================ # Copy this file to .env.local and fill in your values. # Never commit .env.local to version control. # === DATABASE (external Postgres) === # Standard Postgres connection string. DATABASE_URL=postgres://user:password@host:5432/dbname # === BETTER AUTH === # Generate a secret: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" BETTER_AUTH_SECRET=your-random-secret-here BETTER_AUTH_URL=http://localhost:3000 # Google OAuth — create credentials at https://console.cloud.google.com # Authorized redirect URI: /api/auth/callback/google GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= # === ADMIN BOOTSTRAP & AUTH POLICY === # Comma-separated Better Auth user IDs and/or emails granted /admin access. # Set at least one to administer the platform. No self-service admin path exists. ADMIN_USER_IDS= ADMIN_EMAILS= # Require a verified email before sign-in (recommended for production). REQUIRE_EMAIL_VERIFICATION=false # === STORAGE === # Local-disk fallback directory (used only when Spaces below is not configured). STORAGE_DIR=./storage # Object storage (DigitalOcean Spaces, S3-compatible). When all of KEY/SECRET/ # BUCKET are set, uploads and file serving use the bucket instead of local disk. # Keep the bucket PRIVATE — files are served through the auth-gated /api/files route. SPACES_KEY= SPACES_SECRET= SPACES_REGION=nyc3 SPACES_BUCKET= SPACES_ENDPOINT=https://nyc3.digitaloceanspaces.com # Optional: if the Space's CDN is enabled, presigned URLs serve from the edge. SPACES_CDN_ENDPOINT=https://nyc3.cdn.digitaloceanspaces.com # === STRIPE === # Get from: https://dashboard.stripe.com/apikeys STRIPE_SECRET_KEY=sk_test_your-secret-key STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your-publishable-key # No price IDs needed — the app resolves prices by stable lookup keys and # auto-creates them on first checkout (lib/stripe/prices.ts), so going live is a # pure key swap. Optionally pre-create the catalog: node scripts/stripe-setup.mjs # === AI PROVIDER (OpenAI and/or Anthropic) === # The active provider is chosen by an admin in Settings → System. Configure the # key(s) for whichever provider(s) you want available; the app falls back to the # configured one if the selected provider's key is missing. # OpenAI — https://platform.openai.com/api-keys OPENAI_API_KEY=sk-your-api-key # OPENAI_MODEL=gpt-4o-mini # Anthropic (Claude) — https://console.anthropic.com/settings/keys ANTHROPIC_API_KEY= # ANTHROPIC_MODEL=claude-haiku-4-5 # cheapest; use claude-sonnet-5 / claude-opus-4-8 for more capability # === EMAIL (SMTP — e.g. SMTP2GO) === # Any SMTP provider works. Port 465 = implicit SSL; 587/2525 = STARTTLS. SMTP_HOST=mail.smtp2go.com SMTP_PORT=2525 SMTP_USER= SMTP_PASS= EMAIL_FROM=postmaster@yourdomain.com # === ACCOUNTING SYNC (optional — QuickBooks / Xero OAuth) === # Create developer apps and set the redirect URI to # /api/integrations/quickbooks/callback and .../xero/callback # Leave blank to hide/disable a provider. Tokens are encrypted at rest. QBO_CLIENT_ID= QBO_CLIENT_SECRET= QBO_ENVIRONMENT=sandbox XERO_CLIENT_ID= XERO_CLIENT_SECRET= # === E-SIGNATURE (optional — per-landlord: each connects their OWN account) === # DocuSign: register ONE DocuSign app (integration key) here; each landlord then # connects their own DocuSign account via OAuth from Settings → Integrations. # Redirect URI to register in the DocuSign app: /api/esign/docusign/callback # DOCUSIGN_OAUTH_BASE: account-d.docusign.com (demo) or account.docusign.com (prod). DOCUSIGN_CLIENT_ID= DOCUSIGN_CLIENT_SECRET= DOCUSIGN_OAUTH_BASE=account-d.docusign.com # Dropbox Sign: no server credentials — landlords paste their own API key in the # app and set their account callback URL to /api/esign/dropbox_sign/webhook. # DROPBOX_SIGN_TEST_MODE applies test mode to all outbound requests (optional). DROPBOX_SIGN_TEST_MODE=false # === APP === NEXT_PUBLIC_APP_URL=http://localhost:3000 NEXT_PUBLIC_APP_NAME=Property Management Network # Google Search Console verification token (Search Console -> Settings -> HTML tag). # Leave blank in dev; set in production to emit the verification meta tag. GOOGLE_SITE_VERIFICATION= CRON_SECRET=your-random-secret-string # === ANALYTICS (Umami — optional) === # Cookieless page analytics, loaded ONLY in production builds. Defaults point at # the shared phluit Umami instance; override to use a different tracker, or set # the website id to empty to disable. Both are public (they appear in the HTML). NEXT_PUBLIC_UMAMI_SRC=https://fickanalytics.phluit.net/script.js NEXT_PUBLIC_UMAMI_WEBSITE_ID=4066c359-596f-4d0e-9636-c035c2adfbe8 # === ERROR MONITORING (Sentry — optional) === # Paste the DSN from your Sentry project (Settings → Client Keys / DSN). It's # public (ships in the browser bundle). Sentry stays inert until this is set. NEXT_PUBLIC_SENTRY_DSN= # Build-time only: uploads source maps for readable stack traces. Create at # Sentry → Settings → Auth Tokens. Keep secret; leave blank to skip upload. SENTRY_AUTH_TOKEN= # === MAPS / GEOCODING (OpenStreetMap — free, no key) === # Property addresses are geocoded on save via OpenStreetMap Nominatim and shown # on a Leaflet map (both keyless & free). Nominatim's policy requires an # identifying User-Agent — set this to a contact URL or email for production. GEOCODER_USER_AGENT=PropertyManagementNetwork/1.0 (https://propertymanagement.network) # === CLOUDFLARE TURNSTILE (bot protection on auth forms) === # Create a widget at https://dash.cloudflare.com/?to=/:account/turnstile # Leave both blank to disable the captcha (auth forms still work). NEXT_PUBLIC_TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY=