Complete reference for every environment variable recognised by answerLoops.
All variables are set in your .env file (local) or in your hosting provider's environment configuration (production). Variables marked Required must be present or the app will refuse to start.
answerLoops uses Auth.js v5. The correct variable names are AUTH_SECRET and AUTH_URL — notNEXTAUTH_SECRET / NEXTAUTH_URL. If you are migrating from an older deployment, rename them.
PostgreSQL connection string, e.g. postgresql://user:pass@host:5432/dbname
DIRECT_DATABASE_URL
Non-pooled connection string for LISTEN/NOTIFY only (config hot-reload, live team and dashboard updates). Required whenever DATABASE_URL is a pooled connection (Neon -pooler, PgBouncer, etc.) — falls back to DATABASE_URL otherwise
AUTH_SECRET
Random 32-byte secret used to sign Auth.js v5 session tokens and the OAuth state for channel connect flows. Generate with openssl rand -hex 32
AUTH_URL
Full public URL of your deployment, e.g. https://answerloops.com. Used by Auth.js for OAuth callbacks
ENCRYPTION_KEY
32-byte hex key used to encrypt stored API keys at rest. Generate with openssl rand -hex 32. In production (NODE_ENV=production) it is mandatory — saving a credential without it throws instead of storing plaintext
Number of proxies between the public internet and the app. Defaults to 1. Rate limiters resolve the client IP by counting this many entries in from the right of x-forwarded-for, so a caller-supplied prefix on that header can't be used to mint a fresh bucket per request. Set to 2 for Cloudflare in front of a platform load balancer. cf-connecting-ip takes precedence when present
ORIGIN_VERIFY_SECRET
Optional. When set, every public pre-auth POST route (/api/mcp, /api/v1/agent/*, /api/widget/chat) rejects any request missing a matching x-origin-verify header — without it, the proxy-supplied client-IP header can be spoofed by hitting the origin directly instead of through your edge proxy. Requires a matching rule on your CDN/edge proxy; see the self-hosting guide
Optional. Base URL of a dedicated dashboard subdomain (e.g. https://app.example.com) that shares the same deployment as your root domain. When set, marketing-page CTAs link there instead of a relative /dashboard path. Unset by default — most deployments serve everything from one domain
AUTH_COOKIE_DOMAIN
Optional. Shares the Auth.js session cookie across every subdomain of your apex (e.g. .example.com). Required alongside NEXT_PUBLIC_APP_URL, since a session cookie is host-only by default — without it, a user signed in on the root domain appears logged out on the app subdomain
Google is the only sign-in provider answerLoops configures. Both variables are required.
Variable
Description
AUTH_GOOGLE_ID
Google OAuth client ID
AUTH_GOOGLE_SECRET
Google OAuth client secret
Callback URL to register: https://<your-domain>/api/auth/callback/google
DISCORD_CLIENT_ID below is a separate thing — the "connect a Discord server" flow inside the product, not a sign-in method. There is no Discord or GitHub OAuth provider for dashboard sign-in.
Bot token from the Discord Developer Portal. Required on both the app service (for the channel picker UI) and the bot service
DISCORD_CLIENT_ID
Discord application client ID. Optional — when set, onboarding offers a 1-click "Add to Discord" flow instead of asking for a manually-created bot token and channel IDs
DISCORD_APPLICATION_ID
Discord application ID (same value as DISCORD_CLIENT_ID in most setups). Required to register the /ask and /summarize slash commands
DISCORD_GUILD_ID
ID of the Discord server to monitor
BOT_SECRET
Shared secret between the bot and the app for authenticating ingest requests
BOT_TARGET_URL
Base URL the bot posts ingested messages to, e.g. https://answerloops.com — no trailing slash
Full service-account JSON key, as a single-line string. Authenticates outgoing replies to the Chat API.
GOOGLE_CHAT_ENDPOINT_URL
Public HTTP endpoint URL for the Chat app's connection settings. Must exactly match what's configured in Google Cloud — used to verify incoming request tokens.
PEM-encoded private key generated in the GitHub App settings
GITHUB_WEBHOOK_SECRET
Secret used to verify GitHub webhook payloads. Generate with openssl rand -hex 32 — paste without trailing newline. Required — /api/github/webhook returns 503 until it is set
Resend API key — used for both outbound transactional email and the email ingest channel
RESEND_FROM
Default sender address for transactional emails, e.g. [email protected]
RESEND_WAITLIST_FROM
Sender address for waitlist emails (can be the same as RESEND_FROM)
RESEND_WEBHOOK_SECRET
Svix signing secret (whsec_...) for Resend's outbound delivery-status webhooks (bounces/complaints) — bounce/complaint tracking is unavailable without it, sending and inbound ingest still work
GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET
Google Cloud OAuth client for the "Connect Gmail" send-only integration — optional, without it that feature is unavailable
GMAIL_REDIRECT_URI
Optional override for the Gmail OAuth callback URL (defaults to <app-url>/api/email/gmail/callback)
OUTLOOK_CLIENT_ID / OUTLOOK_CLIENT_SECRET
Microsoft Entra app registration for the "Connect Outlook" send-only integration — optional, without it that feature is unavailable
OUTLOOK_REDIRECT_URI
Optional override for the Outlook OAuth callback URL (defaults to <app-url>/api/email/outlook/callback)
Discourse, Circle, and the Notion KB source are configured entirely per-organization — there is no platform-wide app or shared credential to set here. Each org pastes its own token or secret in Settings, encrypted at rest with ENCRYPTION_KEY (above).
Optional. Minutes assumed saved per deflected ticket, used in the ROI-hours-saved calculation. Defaults to a reasonable estimate — see lib/analytics/roi.ts
ROI_STAFF_HOURLY_RATE
Optional. Hourly staff rate used to convert hours saved into a dollar figure. Same defaulting behaviour
Set to cloudonly on answerLoops' own managed SaaS deployment. Determines whether plan-tier feature gating (Discord/Slack integrations, CSAT scoring, simulation, knowledge gap dashboard, custom AI model config) applies at all. Any other value, or unset, means self-hosted: unmetered, every feature unlocked, no Stripe required
STRIPE_SECRET_KEY
Stripe secret API key. Required when DEPLOYMENT_MODE=cloud — its absence there is treated as a misconfiguration (billing shows an error state) rather than falling back to self-hosted/unlimited
STRIPE_WEBHOOK_SECRET
Signing secret for the Stripe webhook endpoint (/api/billing/webhook), used to verify subscription lifecycle events
STRIPE_PUBLISHABLE_KEY
Publishable key (pk_...) for the embedded checkout form on /checkout. Read at request time — deliberately not NEXT_PUBLIC_-prefixed, since that prefix is substituted at build time and could never be set by someone running a prebuilt image. Safe to expose in client code by design — it can only create payment attempts, never read or move money. Without it that page shows a disabled state instead of a card form
Stripe Price IDs used for each paid plan's checkout session. Each var must hold the price for the plan it names — the amount shown in the app comes from the code, while the amount charged comes from Stripe, so a mismatch bills the wrong figure without erroring
Yearly Stripe Price IDs for the same three plans. Selected when a visitor picks annual billing on the pricing page. A plan without one declines annual checkout rather than billing the monthly price.
DEPLOYMENT_MODE was introduced to stop deployment type from being inferred from whether STRIPE_SECRET_KEY happened to be set — a missing or rotated key on the cloud deployment used to silently degrade every paying org to unmetered/unlimited instead of surfacing as a misconfiguration.