# ============================================================ # REQUIRED — the app won't start without these. # ============================================================ # Supabase (Project Settings → API) NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key # Supabase service-role key. Bypasses RLS; used only by server-side # routes (the webhook, automation engine, and the public API key # auth path — see docs/public-api.md). Keep this secret — never # paste it into client code. # # Note: the public API (/api/v1) needs no new env var. API keys are # created in the dashboard (Settings → API keys) and stored hashed # in the database; this service-role key is what lets the auth path # look a presented key up without a user session. SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # WhatsApp token encryption (64 hex chars = 32 bytes, AES-256-GCM). # Generate with: # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # Rotating this value orphans every token encrypted under the previous # key — users have to re-save their WhatsApp settings to reconnect. ENCRYPTION_KEY=your-64-char-hex-key-here # Meta App Secret (Meta for Developers → App Settings → Basic). # Verifies the HMAC-SHA256 signature on every inbound webhook POST. # Required — without it the webhook rejects every request. META_APP_SECRET=your-meta-app-secret # ============================================================ # RECOMMENDED — safe defaults exist but you'll want to set these. # ============================================================ # Canonical public URL of this deployment (scheme + host, no trailing # slash). Used for the sitemap and OG images. Routes that need a # self-referential URL (invite links from /api/account/invitations, # any future email-bound link) derive the origin from the request # itself, so this variable is *only* needed when the request-derived # origin would be wrong — e.g. when generating links from a cron job # or a background worker that has no incoming request. NEXT_PUBLIC_SITE_URL=https://crm.example.com # ============================================================ # OPTIONAL — only needed if you use the feature. # ============================================================ # Defense-in-depth allow-list for the hostnames that # /api/account/invitations is willing to publish in invite URLs. # Comma-separated, no scheme, no port (just hostnames). # # Why this exists: when NEXT_PUBLIC_SITE_URL is unset, invite URLs # are derived from the incoming request's `Host` / `X-Forwarded- # Host` header. On a typical proxied deploy the proxy sets these # to your canonical hostname and they're trustworthy. On a *bare* # deployment exposed to the public internet, an attacker could # POST to the API directly with a spoofed `Host: phishing.example` # and receive an invite URL pointing at their site. # # When this var is set, hostnames not on the list are rejected # (the request falls through to the wacrm.tech fallback with a # console.warn). When unset, behavior is unchanged from earlier # versions — the request-derived host is trusted. # # Most operators don't need this: setting NEXT_PUBLIC_SITE_URL to # your canonical URL already pins invite links there. This is for # operators who want belt-and-braces or run multi-tenant setups # where one app instance serves several hostnames. # # Example: # ALLOWED_INVITE_HOSTS=crm.example.com,crm-staging.example.com # Shared secret protecting GET /api/automations/cron. Required if you # use Wait steps in automations (a scheduled pinger drains pending # executions). Generate any long random string: # openssl rand -hex 32 # See docs/automations-and-cron.md. # AUTOMATION_CRON_SECRET=generate-a-long-random-string # Meta App ID (Meta for Developers → App Settings → Basic). Required to # create/edit message templates with an IMAGE header: Meta only accepts a # Resumable-Upload media handle (not a plain URL) as the header sample, and # that upload is app-scoped. Without it, image-header template submission # returns a clear error; text/body-only templates are unaffected. Pair # with META_APP_SECRET. # META_APP_ID=your-meta-app-id # When "true", POST /api/whatsapp/templates/submit skips the Meta call # and stores the row with a synthetic `dry-run-` meta_template_id. # Set this in CI and local development so you can exercise the full # template UI without a real WABA. Leave unset (or "false") in prod. # WHATSAPP_TEMPLATES_DRY_RUN=true # ------------------------------------------------------------------ # AI reply assistant (optional) # ------------------------------------------------------------------ # The AI assistant is bring-your-own-key: each account pastes its own # OpenAI or Anthropic key under Settings → AI Assistant. The key is # stored AES-256-GCM-encrypted with ENCRYPTION_KEY (above) — there is # NO global provider key env var, and nothing here is required for the # feature to work. The two vars below only tune behaviour. # # The AI knowledge base (migration 030) uses Postgres full-text search # out of the box. Optional semantic search needs the `pgvector` # extension — migration 030 runs `CREATE EXTENSION IF NOT EXISTS vector` # (Supabase has it available) — plus a per-account embeddings key set in # Settings → AI Assistant. Still no env var required. # Per-call timeout for provider requests, in milliseconds. Default 30000. # AI_REQUEST_TIMEOUT_MS=30000 # How many recent text messages of a conversation to send the model as # context (draft + auto-reply). Default 20. # AI_CONTEXT_MESSAGE_LIMIT=20