Saltar a contenido

Environment Variables

Available to: Platform Admin only

Every Django setting that comes from the environment is loaded in config/settings.py via python-decouple. Production deploys use DigitalOcean App Platform (spec: .do/app.yaml) with components web, worker, beat, frontend, and migrate.

Related guides: Email Delivery, Deployment.


Rules of thumb

  1. Shared secrets must match across web, worker, and beat — especially SECRET_KEY and TENANT_ENCRYPTION_KEY.
  2. Declaring a key in app.yaml is not enough — for type: SECRET entries you must set the value in the App Platform dashboard. An empty secret behaves like an empty string at runtime.
  3. Email sends on the worker — almost all email uses Celery .delay(). Mailgun variables on web alone are insufficient; worker must have them.
  4. No Django SMTP — this app does not use EMAIL_HOST / EMAIL_BACKEND. Transactional email goes through the Mailgun HTTP API (apps/notifications/email.py).

Which component needs what

Category web worker beat frontend migrate
Core Django Yes Yes Yes Yes
Database / Redis Yes Yes Yes Yes
Auth0 JWT Yes Yes BUILD
Auth0 MGMT (invites) Yes
Mailgun (email) Yes* Yes
Twilio (SMS) Yes Yes
AWS / Spaces Yes Yes Yes
CORS Yes
Frontend build BUILD

*Web queues email tasks; the worker executes them.


Email delivery — required variables

These four variables are required for email to work. If either MAILGUN_API_KEY or MAILGUN_DOMAIN is empty, sends are skipped and EmailLog records skipped_no_config. See Email Delivery.

Variable Example Where to get it
MAILGUN_API_KEY key-xxxxxxxx Mailgun dashboard → API Keys → Private API key
MAILGUN_DOMAIN mg.hardhatflow.com Mailgun → Sending → verified domain (or sandbox domain for testing)
DEFAULT_FROM_EMAIL Hardhat Flow <[email protected]> Must use a domain that matches MAILGUN_DOMAIN
FRONTEND_URL https://hardhatflow.com Public URL of the frontend app (links in email bodies)

Optional:

Variable Purpose
PLATFORM_ADMIN_NOTIFY_EMAILS Comma-separated fallback recipients for access-request admin alerts when no platform-admin users exist

Operator checklist (App Platform)

  1. Open hardhat-flowworkerEnvironment Variables.
  2. Add MAILGUN_API_KEY, MAILGUN_DOMAIN, DEFAULT_FROM_EMAIL, FRONTEND_URL with real values. Encrypt secrets.
  3. Repeat the same four variables on web (same values).
  4. Redeploy the app (env changes require a new deployment).
  5. Trigger a test (invite a user or submit a bid for approval).
  6. Check django-admin → Companies → Email logs — expect sent, not skipped_no_config.

Mailgun sandbox

Sandbox domains only deliver to authorized recipients in the Mailgun dashboard. For production, verify a custom sending domain and update MAILGUN_DOMAIN and DEFAULT_FROM_EMAIL to match.


Variable catalog

Core Django

Variable Default Components Description
DJANGO_SETTINGS_MODULE web, worker, beat, migrate Always config.settings
SECRET_KEY insecure dev key web, worker, beat, migrate Django signing key; must match across components
DEBUG False web Never True in production
ALLOWED_HOSTS localhost,127.0.0.1 web Comma-separated API hostnames; code auto-adds .ondigitalocean.app when DEBUG=False
DJANGO_LOG_LEVEL INFO web, worker, beat Python logging level
DJANGO_ENV development web Sentry environment tag
CSRF_TRUSTED_ORIGINS auto from ALLOWED_HOSTS web HTTPS origins for django-admin CSRF; optional override (comma-separated, include https://)
COLLECT_DOCS_STATIC True Docker build only Set False during Docker collectstatic (see Dockerfile)

Database

Variable Default Components Description
DATABASE_URL web, worker, beat, migrate Managed MySQL; use ${db.DATABASE_URL} on App Platform
DB_NAME hardhat_flow local dev Used when DATABASE_URL is unset
DB_USER root local dev
DB_PASSWORD local dev
DB_HOST localhost local dev
DB_PORT 3306 local dev

Redis / Celery

Variable Default Components Description
REDIS_URL redis://localhost:6379/0 web, worker, beat Celery broker and cache; use ${redis.REDIS_URL} on App Platform

Multi-tenancy

Variable Default Components Description
TENANT_ENCRYPTION_KEY web, worker, beat, migrate Fernet key for per-tenant DB credentials. Generate once; rotating invalidates stored tenant credentials

Generate:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Auth0 — API authentication (JWT)

Variable Default Components Description
AUTH0_DOMAIN web, worker Tenant domain without https:// (e.g. dev-xxx.us.auth0.com)
AUTH0_API_AUDIENCE web, worker API identifier URL
AUTH0_JWKS_CACHE_TTL_SEC 300 web JWKS cache TTL (seconds)
AUTH0_JWKS_FETCH_TIMEOUT_SEC 5.0 web JWKS fetch timeout (seconds)
AUTH0_REQUIRE_EMAIL_VERIFIED True web Reject tokens without verified email claim

Auth0 — user invites (Management API)

Variable Default Components Description
AUTH0_MGMT_CLIENT_ID web M2M application client ID
AUTH0_MGMT_CLIENT_SECRET web M2M application secret
AUTH0_SPA_CLIENT_ID web SPA client ID for password-change ticket URLs in welcome emails
AUTH0_DB_CONNECTION Username-Password-Authentication web Auth0 database connection name
AUTH0_INVITE_ENABLED True web Set False to skip Auth0 provisioning (tests/local dev)

Invite welcome email only sends after Auth0 provisioning succeeds. Failures appear as AUTH0_INVITE_FAILED in tenant ActivityLog, not in EmailLog.

Email (Mailgun)

Variable Default Components Description
MAILGUN_API_KEY "" worker, web Empty = all sends skipped (skipped_no_config)
MAILGUN_DOMAIN "" worker, web Sending domain
DEFAULT_FROM_EMAIL Hardhat Flow <[email protected]> worker, web From address
FRONTEND_URL https://app.hardhatflow.com worker, web Public frontend URL for email links
PLATFORM_ADMIN_NOTIFY_EMAILS "" web Optional comma-separated fallback for access-request alerts

SMS (Twilio)

Variable Default Components Description
TWILIO_ACCOUNT_SID web, worker Twilio account SID
TWILIO_AUTH_TOKEN web, worker Twilio auth token

File storage (DigitalOcean Spaces)

Variable Default Components Description
USE_S3 False web, worker, beat Set True in production
AWS_ACCESS_KEY_ID web, worker, beat Spaces access key
AWS_SECRET_ACCESS_KEY web, worker, beat Spaces secret
AWS_STORAGE_BUCKET_NAME web, worker, beat Bucket name
AWS_S3_ENDPOINT_URL app-level + components e.g. https://nyc3.digitaloceanspaces.com
AWS_S3_REGION_NAME us-east-1 / nyc3 web, worker, beat Region slug
AWS_S3_CUSTOM_DOMAIN web Optional CDN domain
AWS_QUERYSTRING_EXPIRE 3600 web Presigned URL TTL (seconds)

Spaces endpoint override

Do not re-declare AWS_S3_ENDPOINT_URL as an empty encrypted secret on a component — it can override the app-level value with a blank string and break boto3.

CORS

Variable Default Components Description
CORS_ALLOWED_ORIGINS http://localhost:3000 web Comma-separated frontend origin(s)

Observability

Variable Default Components Description
SENTRY_DSN web, worker Sentry error tracking DSN

Backups

Variable Default Components Description
BACKUP_RETENTION_DAYS 30 web Legacy setting; retention uses slot pruning

Frontend (build-time only)

These are baked into the static export at build time. They do not affect Django runtime.

Variable Components Description
NEXT_PUBLIC_API_URL frontend BUILD Django web public URL without a /api suffix
NEXT_PUBLIC_AUTH0_DOMAIN frontend BUILD Same value as AUTH0_DOMAIN
NEXT_PUBLIC_AUTH0_CLIENT_ID frontend BUILD Auth0 SPA application client ID
NEXT_PUBLIC_AUTH0_AUDIENCE frontend BUILD Same value as AUTH0_API_AUDIENCE
NODE_ENV frontend BUILD production
NEXT_BUILD frontend BUILD Set 1 for static export builds

Do not add Mailgun variables to the frontend component.

Local / CI only (not production runtime)

Variable Purpose
DB_* Local MySQL when DATABASE_URL is unset
JIRA_* scripts/jira_tasks.py only
E2E_*_EMAIL, E2E_*_PASSWORD Playwright E2E auth setup
PLAYWRIGHT_BASE_URL E2E base URL (default http://localhost:3000)

Common misconfigurations

Symptom Likely cause
EmailLog skipped_no_config MAILGUN_* missing on worker (or empty secret value in DO)
No EmailLog row after event Worker not running; notification disabled in tenant Settings; or invite never reached Mailgun step
EmailLog sent but no inbox delivery Mailgun sandbox recipient not authorized; spam/suppression — check provider_message_id in Mailgun
Wrong links in emails FRONTEND_URL set to API host instead of frontend host
Mailgun API error DEFAULT_FROM_EMAIL domain does not match MAILGUN_DOMAIN
Double /api in browser NEXT_PUBLIC_API_URL incorrectly includes /api suffix
django-admin CSRF 403 Missing CSRF_TRUSTED_ORIGINS / proxy SSL header (fixed in code; redeploy required)

Verification

Worker console (App Platform → worker → Console):

from django.conf import settings
print("mailgun", bool(settings.MAILGUN_API_KEY), settings.MAILGUN_DOMAIN)
print("from", settings.DEFAULT_FROM_EMAIL)
print("frontend", settings.FRONTEND_URL)

Expected: mailgun True <your-domain>, non-empty from address and frontend URL.

After a test event: django-admin → Companies → Email logs — filter by recent created_at and check status.