Configuration
The backend is configured entirely from environment variables. python-dotenv loads .env from the repo root in manage.py, wsgi.py and celery.py; inside Docker, compose passes the same names through the x-app-env anchor. Values are read with os.getenv in EMAW_BACKEND/settings/*.py.
This page lists variable names. Values live in the shared .env distributed through the password manager and must never be committed, pasted into chat or written into documentation. The authoritative list, with defaults and behaviour notes, is CLAUDE.md ("Settings & Environment").
Required to boot
| Variable | Notes |
|---|---|
DJANGO_SETTINGS_MODULE | EMAW_BACKEND.settings.development or .production (defaults to development) |
SECRET_KEY | Also signs JWTs (SIMPLE_JWT["SIGNING_KEY"]) |
JWT_SIGNING_KEY | Read into settings but not used for signing today; kept for forward compatibility |
EMAW_SALT | Username hashing salt. Must match the database you connect to or nobody can log in. Production reads it from Key Vault instead (USE_LOCAL_SALT) |
DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT | PostgreSQL. DB_HOST is a Tailscale IP in dev. DB_PORT defaults to 5432 |
REDIS_URL | Default redis://redis:6379/0 in compose, redis://localhost:6379/0 on the host. Celery uses databases /1 (broker) and /2 (results) unless CELERY_BROKER_URL / CELERY_RESULT_BACKEND override |
TUNNEL_TOKEN | For the compose tunnel service only |
ALLOWED_HOSTS in .env is not read; the list is hard-coded per settings module.
Email
| Variable | For |
|---|---|
MS_TENANT_ID, MS_CLIENT_ID, MS_CLIENT_SECRET, MS_SENDER_UPN | Transactional mail via Microsoft Graph (sign-up codes, password reset, receipts, order emails). DEFAULT_FROM_EMAIL falls back to support@emaw-app.com |
CONTACT_FORM_RECIPIENTS | Where the website contact form goes |
ACS_CONNECTION_STRING, ACS_SENDER_ADDRESS, ACS_SENDER_NAME, ACS_REPLY_TO, ACS_EVENTGRID_WEBHOOK_KEY | Marketing campaigns via Azure Communication Services |
CAMPAIGN_SENDING_ENABLED, CAMPAIGN_SEND_ALLOWED_GROUP_IDS, CAMPAIGN_SEND_RATE_PER_MINUTE, CAMPAIGN_BATCH_SIZE, CAMPAIGN_LARGE_THRESHOLD, CAMPAIGN_REQUIRE_TEST_SEND | Campaign send controls. Keep sending off in dev; if on, allow only group 36 |
PUBLIC_BASE_URL, WEBSITE_BASE_URL, CAMPAIGN_POSTAL_ADDRESS | Links and footer in emails |
Storage
| Variable | For |
|---|---|
AZURE_BLOB_KEY | Azure Blob (emawblob/media), Django's default storage. Account name and container are hard-coded |
AZURE_BLOB_SAT | SAS token used for generated verse audio |
R2_ACCOUNT_ID, R2_BUCKET, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_PUBLIC_BASE_URL | Cloudflare R2 for website resource media. Uploads return 503 until the four credentials are set |
Payments and commerce (Stripe)
| Variable | For |
|---|---|
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_API_VERSION | Stripe client; API version is pinned (default 2026-09-30.endive). Test keys in dev |
CHECKOUT_RETURN_URL, CHECKOUT_SUCCESS_URL, CHECKOUT_CANCEL_URL | Where Stripe sends the browser back |
STORE_PAYMENT_METHOD_CONFIGURATION, STORE_AUTOMATIC_TAX, STORE_PRODUCT_TAX_CODE, STORE_ORDER_NOTIFY_RECIPIENTS, STORE_SHIPPING_COUNTRIES, STORE_LOW_STOCK_THRESHOLD | Store |
EVENTS_PAYMENT_METHOD_CONFIGURATION, EVENTS_NOTIFY_RECIPIENTS | Events (default to the store values) |
DONATIONS_PAYMENT_METHOD_CONFIGURATION, DONATIONS_STRIPE_PRODUCT_ID, DONATIONS_PORTAL_CONFIGURATION, DONATIONS_NOTIFY_RECIPIENTS, DONATIONS_FEE_PERCENT, DONATIONS_FEE_FIXED_CENTS, DONATIONS_MIN_AMOUNT_CENTS, DONATIONS_MAX_AMOUNT_CENTS, DONATIONS_ORG_LEGAL_NAME, DONATIONS_ORG_EIN | Donations |
Shipping
| Variable | For |
|---|---|
SHIPPING_RATE_SOURCE | fulfillment (historic warehouse costs, the default) or carriers (live quotes) |
STORE_FALLBACK_SHIPPING_CENTS, STORE_FALLBACK_SHIPPING_LABEL, SHIPPING_RATE_CACHE_SECONDS | Flat fallback (default 599) and quote cache |
SHIPPING_ORIGIN_LINE1, SHIPPING_ORIGIN_CITY, SHIPPING_ORIGIN_STATE, SHIPPING_ORIGIN_POSTAL_CODE, SHIPPING_BOXES_JSON | Ship-from address and box sizes |
SHIPPING_HISTORY_RECENT_DAYS, SHIPPING_HISTORY_HALF_LIFE_DAYS, FULFILLMENT_TIME_ZONE | Fulfilment-history pricing |
UPS_CLIENT_ID, UPS_CLIENT_SECRET, UPS_ACCOUNT_NUMBER, UPS_BASE_URL, UPS_SERVICES | UPS (used only when all credentials are set) |
USPS_CLIENT_ID, USPS_CLIENT_SECRET, USPS_BASE_URL, USPS_MAIL_CLASSES, USPS_PRICE_TYPE | USPS |
Warehouse (Deposco)
| Variable | For |
|---|---|
DEPOSCO_USERNAME, DEPOSCO_PASSWORD, DEPOSCO_CODE | All three turn the integration on; DEPOSCO_ENABLED=0 forces it off |
DEPOSCO_BASE_URL | Defaults to the North America sandbox; sandbox in dev |
DEPOSCO_BUSINESS_UNIT, DEPOSCO_ORDER_PREFIX (distinct per environment), DEPOSCO_ORDER_SOURCE, DEPOSCO_SHIP_VIA_MAP (JSON), DEPOSCO_TIMEOUT, DEPOSCO_MIN_REQUEST_INTERVAL | Order mapping |
DEPOSCO_SYNC_INTERVAL_MINUTES, DEPOSCO_SYNC_MAX_AGE_DAYS, DEPOSCO_INVENTORY_SYNC_MINUTES | Beat schedule |
AI and search
| Variable | For |
|---|---|
OpenAI | The OpenAI API key (non-standard name; OPENAI_API_KEY is a fallback). Used by verse finder, TTS, Whisper recitation analysis, email template drafts |
AI_SEARCH_ENABLED, AI_SEARCH_BASE_VERSION | Registers /rest/bibles/search/ and /similar/; base version defaults to NIV |
QDRANT_HOST, QDRANT_PORT, QDRANT_TIMEOUT | Default qdrant / 6333 in compose |
EMBEDDINGS_DIR, EMBEDDINGS_URL, QDRANT_BATCH_SIZE, QDRANT_MAX_RETRIES, QDRANT_BACKOFF_SECONDS | load_bible_embeddings |
AI_TEMPLATE_ENABLED, AI_TEMPLATE_MODEL | AI email template drafts (default model gpt-4o) |
Google
| Variable | For |
|---|---|
GOOGLE_SIGNIN_CLIENT_IDS | Comma-separated web/iOS/Android OAuth client ids whose ID tokens may sign in app users. Empty = Google sign-in off |
GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, GOOGLE_OAUTH_REDIRECT_URIS, GOOGLE_TOKEN_KEY | Separate internal OAuth client for Google Meet rooms, calendar invites, recordings and attendance; GOOGLE_TOKEN_KEY defaults to a derivation of SECRET_KEY |
Setup runbook: docs/GOOGLE_SIGN_IN_SETUP.md.
CRM and geography
| Variable | For |
|---|---|
AZURE_MAPS_KEY, GEOCODING_ENABLED | Geocoding contacts and venues |
GEOGRAPHY_ASSIGN_SYNC_MAX, GEOGRAPHY_RUN_STALE_MINUTES | Bulk region/area assignment runs |
CRM_CONTACT_LIST_CACHE_SECONDS | Redis cache for contact list pages (0 disables) |
PLANNING_CENTER_CLIENT_ID, PLANNING_CENTER_SECRET, PCO_MIGRATION_COMPLETE | Planning Center importers (migration only) |
APP_EXTRA_ORIGINS, COMMERCE_EXTRA_ORIGINS | Extra CORS / hand-off / checkout origins beyond the hard-coded lists |
Security and logging
| Variable | For |
|---|---|
CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD | With both set, staff endpoints require a valid Cloudflare Access JWT. Unset locally |
JWT_ROTATE_REFRESH_TOKENS, REFRESH_ROTATION_GRACE_SECONDS | Global refresh-token rotation (off until old mobile builds retire) |
TRUST_CF_CONNECTING_IP | Use Cloudflare's header for the client IP |
LOG_DIR, DJANGO_LOG_LEVEL, LOG_TO_FILE, LOG_JSON | Logging (see Logs and monitoring) |
Tests
TEST_DB_NAME, TEST_DB_USER, TEST_DB_PASSWORD, TEST_DB_HOST, TEST_DB_PORT replace the matching DB_* for the test process only. See Testing.
Known gaps
- These are read by settings but missing from the compose
x-app-envanchor, so they do not reach the containers unless added:SHIPPING_RATE_SOURCE,SHIPPING_HISTORY_RECENT_DAYS,SHIPPING_HISTORY_HALF_LIFE_DAYS,FULFILLMENT_TIME_ZONE,EVENTS_PAYMENT_METHOD_CONFIGURATION,EVENTS_NOTIFY_RECIPIENTS,CONTACT_FORM_RECIPIENTS,GEOGRAPHY_ASSIGN_SYNC_MAX,GEOGRAPHY_RUN_STALE_MINUTES,CRM_CONTACT_LIST_CACHE_SECONDS,OPENAI_API_KEY. - Present in
.envbut never read:EMAIL_PASSWORD,QUICK_BOOKS_CLIENT_ID,QUICK_BOOKS_CLIENT_SECRET,ALLOWED_HOSTS.
Tracked on the known gaps page.