Runtime
¶ DigitalOcean App Platform
Encrypted env vars on the app components.
Security
Secrets live in DigitalOcean App Platform encrypted env vars and GitHub Actions secrets. Production secrets must be freshly generated and never reused from development.
Where secrets live
Other sensitive config
JWT_SECRET
Database URLs
Redis URLs
Spaces keys
CLOUD_TOKEN_KEY
META_APP_SECRET
GEMINI_API_KEY
DISCIPLINARY_BRIDGE_TOKEN
UTOPIA_SSO_SHARED_SECRET
GITHUB_ISSUES_TOKEN
RESEND_API_KEY
INTEGRATION_SHARED_KEY
CALL_LOG_INGEST_KEY
TELEGRAM_BOT_TOKEN
TELEGRAM_CHAT_ID
Feature flags and account config
Developer Team account
PLATFORM_ACCOUNT_EMAILNames the Developer Team login. In Business workspaces that account is auto-added as an admin at creation; in Personal workspaces it is refused on every join path (invite accept, share-link join). Empty disables both behaviours.
This value grants access to customer inboxes if set to a registered active user. Treat it as sensitive: store it in secrets and CI/CD env, and restrict who can change it. Email matching is case-insensitive (users.email is CITEXT), and the account must already be registered and active to be auto-added.
Default false
REPLY_COPILOT_CHANNEL_FIREWALLDeclared in apps/api/app/config.py. When true, the Reply Copilot scopes saved replies, tone rows, flows, and context checks to the conversation's channel (line), preventing cross-line content leakage. Set it to true on the DigitalOcean api service and restart to enable.
0149_message_flow_channels table, and turning it off needs no data change.
In-process drain
When IQ_FEEDBACK_V2_DRAIN_ENABLED=1 and the Hub source config's drain_enabled is true, the API registers one periodic task that calls run_drain(limit=10) each tick. The loop is inert while the flag is off, and with it off the operator module is not imported and no task is created, keeping CLI-only drain behaviour.
IQ_FEEDBACK_V2_DRAIN_INTERVAL_SECONDSTick interval as text, default "30". ASCII digits only, range 5–300; an empty string uses the default. Any invalid value prevents the loop from starting and logs iq_feedback_v2_drain_interval_invalid.
Safe with several workersrun_drain claims rows with FOR UPDATE SKIP LOCKED, so multiple API workers cannot duplicate deliveries. The background task is single-flight per worker and cancels cleanly on shutdown.
IQ rules feed
These variables control the opt-in feature that fetches a line's declared rules from Reply IQ before generating a draft. The path is inert while IQ_RULES_FEED_ENABLED is unset or 0, and no request is made.
IQ_RULES_FEED_ENABLEDflagDefault 0. Flip to 1 to enable the feed for the API service.
IQ_RULES_FEED_URLconfigHTTPS endpoint of the Reply IQ rules feed. Use the production IQ host in production; loopback targets are allowed for tests only.
IQ_RULES_FEED_KEY_IDconfigThe key identifier presented to IQ (alphanumeric, _ and - shape).
IQ_RULES_FEED_KEYsecretbase64url without padding of exactly 32 bytes. Store it in the secret manager, and keep it distinct from every other IQ_* key: never reuse keys across IQ lanes.
IQ_RULES_FEED_TIMEOUT_SECONDSconfigPer-request timeout, default 2.
IQ_RULES_FEED_CACHE_SECONDSconfigSuccessful response cache TTL, default 60.
Enable IQ's side first. Turn on the matching Reply IQ config (its IQ_RULES_FEED_KEYS_JSON plus enable flag) before enabling this env var, otherwise IQ returns 401 and ReplyBox silently falls back to the previous behaviour. Never log the raw key, URL, or response body.
Rankings and CI
LEADERBOARD_API_URLconfigURL of the canonical sales leaderboard feed (defaults provided in code, pointing at production). Override it for staging or test environments. ReplyBox does not recompute sales; it trims and serves the upstream payload.
CALLTRACKER_RANKING_URLconfigCall-tracker per-handset ranking feed. Empty means the phone leg reports “not configured” and the WhatsApp leg is served alone.
CALLTRACKER_RANKING_KEYsecretShared key for the call-tracker feed.
REPLYBOX_READONLY_DATABASE_URLsecretA read-only Postgres URL used by CI post-deploy checks to count connected WhatsApp lines without write privileges. Add it to the repository Secrets (Settings → Secrets). Make it a read-only role; if the secret is missing, the CI baseline step fails loudly rather than passing vacuously.
CALLTRACKER_RANKING_* is a visible degraded state, not a 503. Both legs of the missed-call board degrade independently, and ReplyBox shows which source is missing rather than silently hiding data.
Bounded upstreamLeaderboard upstream timeouts are bounded, and caches act as the last good copy.
No caller identifiersThe ranking services never surface caller numbers or JIDs: aggregated queries return counts and labels only.
Never
Ops watchdog
TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are sensitive secrets used only by the ops watchdog command. Treat them like other production secrets.
--dry-run to test.
Run locally with the scriptscripts/watchdog.ps1 imports only DATABASE_URL, REDIS_URL, TELEGRAM_BOT_TOKEN, and TELEGRAM_CHAT_ID from apps/api/prod.env, and removes them from the session when finished.
DigitalOcean actionsFor restart alerts and DB firewall updates the script relies on doctl or DIGITALOCEAN_TOKEN.
Don't leave prod credentials in your shell. Load them only temporarily for the run and remove them immediately afterwards. Do not persist prod tokens in your shell; the script is designed to avoid leaving credentials behind.