Environment Variables
This document provides a comprehensive overview of all environment variables used in the Messages application. These variables are organized by service and functionality.
Development Environment
Environment Files Structure
The application uses a new environment file structure with .defaults and .local files:
*.defaults - Committed default configurations
*.local - Gitignored local overrides (created by make bootstrap)
Available Environment Files
backend.defaults - Main Django application settings
common.defaults - Shared settings across services
frontend.defaults - Frontend configuration
postgresql.defaults - PostgreSQL database configuration
keycloak.defaults - Keycloak configuration
mta-in.defaults - Inbound mail server settings
mta-out.defaults - Outbound mail server settings
crowdin.defaults - Translation service configuration
Core Application Configuration
Django Settings
| Variable |
Default |
Description |
Required |
DJANGO_CONFIGURATION |
Development |
Django configuration class to use (Development, Production, Test, etc.) |
Required |
DJANGO_SECRET_KEY |
None |
Secret key for cryptographic signing |
Required |
DJANGO_ALLOWED_HOSTS |
[] |
List of allowed hostnames |
Required |
DJANGO_SETTINGS_MODULE |
messages.settings |
Django settings module |
Required |
DJANGO_SUPERUSER_PASSWORD |
admin |
Default superuser password for development |
Dev |
DJANGO_DATA_DIR |
/data |
Base directory for data storage |
Optional |
DJANGO_ADMIN_URL |
admin |
admin route (must not be ended by /) |
Optional |
INSTANCE_URL |
None |
Public base URL of this instance — the scheme+host that serves both the API and the web app (e.g. https://messages-public-url.example.com). Used to build absolute links back into the product; currently emitted as the X-StMsg-Instance header on outbound webhooks (omitted when unset). |
Optional |
USE_X_FORWARDED_FOR |
False |
Trust the X-Forwarded-For header to determine the client IP (enable only behind a trusted proxy that sets it). |
Optional |
DATA_UPLOAD_MAX_MEMORY_SIZE |
2621440 |
Django's max request body (bytes) buffered in memory before rejecting (2.5MB). |
Optional |
Database Configuration
PostgreSQL (Main Database)
| Variable |
Default |
Description |
Required |
DATABASE_URL |
None |
Complete database URL (overrides individual DB_* vars) |
Optional |
DB_ENGINE |
django.db.backends.postgresql_psycopg2 |
Database engine |
Optional |
DB_HOST |
postgresql |
Database hostname (container name) |
Optional |
DB_NAME |
messages |
Database name |
Optional |
DB_USER |
user |
Database username |
Optional |
DB_PASSWORD |
pass |
Database password |
Optional |
DB_PORT |
5432 |
Database port |
Optional |
PostgreSQL (Keycloak)
| Variable |
Default |
Description |
Required |
POSTGRES_DB |
messages |
Keycloak database name |
Dev |
POSTGRES_USER |
user |
Keycloak database user |
Dev |
POSTGRES_PASSWORD |
pass |
Keycloak database password |
Dev |
Redis Configuration
| Variable |
Default |
Description |
Required |
REDIS_URL |
redis://redis:6379 |
Redis connection URL (internal) |
Optional |
CELERY_BROKER_URL |
redis://redis:6379 |
Celery message broker URL (internal) |
Optional |
CACHES_DEFAULT_TIMEOUT |
30 |
Default cache timeout in seconds |
Optional |
Note: For external Redis access, use localhost:8913. For internal container communication, use redis:6379.
OpenSearch Configuration
| Variable |
Default |
Description |
Required |
OPENSEARCH_URL |
["http://opensearch:9200"] |
OpenSearch hosts list |
Optional |
OPENSEARCH_CA_CERTS |
None |
Path to a CA bundle for verifying the OpenSearch TLS certificate (for https:// hosts with a private CA). |
Optional |
OPENSEARCH_TIMEOUT |
20 |
OpenSearch query timeout (seconds) for unitary requests |
Optional |
OPENSEARCH_BULK_TIMEOUT |
60 |
OpenSearch request timeout (seconds) applied to bulk indexation calls. Raise it if full reindex (make search-index) hits timeouts on large payloads. |
Optional |
OPENSEARCH_BULK_MAX_BYTES |
26_214_400 |
Flush threshold (bytes) for bulk indexation payloads; default 25 MiB. Once accumulated actions exceed this, opensearch-py emits a sub-chunk HTTP request. Note: this is a batching threshold, not a per-document cap — a single oversized document is still sent as its own chunk. Keep well under the OpenSearch server http.max_content_length |
Optional |
OPENSEARCH_BULK_CHUNK_SIZE |
50 |
Number of thread documents (and their child message documents) accumulated before a bulk flush in reindex_bulk_threads. Lower values reduce per-request cluster pressure (heap, queue depth) at the cost of more round-trips. Lower this if you see 503s on bulk requests. |
Optional |
OPENSEARCH_MAX_RETRIES |
3 |
Transport-level retry budget on the OpenSearch client. The opensearch-py transport already retries on 502/503/504 (DEFAULT_RETRY_ON_STATUS); this just exposes the count so it can be raised above the library default. Whatever exhausts this budget is wrapped as TransientTransportError and handed to Celery autoretry (5 attempts, exponential backoff up to 600s). |
Optional |
OPENSEARCH_INDEX_THREADS |
True |
Enable thread indexing |
Optional |
SEARCH_REINDEX_TASKS_INTERVAL |
30 |
Interval (seconds) between Celery Beat runs of process_pending_reindex_task, which drains the reindex and delete coalescing buffers and enqueues bulk thread tasks. Longer values cut Celery/OpenSearch load at the cost of search-result staleness. |
Optional |
SEARCH_FLUSH_BATCH_SIZE |
1000 |
Maximum number of thread / message IDs handed to a single bulk_*_task call. This is the unit of parallelism, retry granularity and worker occupation for catch-up flows. Lower means more, shorter tasks (better parallelism, cheaper retries on failure); higher means fewer, longer tasks (less broker chatter but worse failure isolation). |
Optional |
SEARCH_FLUSH_MAX_BATCHES |
10 |
Maximum number of bulk_*_task calls a single Beat tick is allowed to enqueue, shared across the three handoffs (reindex / thread-delete / message-delete). Bounds catch-up bursts so a huge backlog is spread across several ticks rather than flooding the broker in one go. Effective per-tick capacity is roughly SEARCH_FLUSH_BATCH_SIZE × SEARCH_FLUSH_MAX_BATCHES IDs. |
Optional |
Mail Processing Configuration
MTA Settings
| Variable |
Default |
Description |
Required |
MTA_OUT_MODE |
direct |
Outbound MTA mode ('direct' or 'relay') |
Required |
MTA_OUT_RELAY_HOST |
mta-out:587 |
Outbound SMTP server host for relay mode |
Required |
MTA_OUT_RELAY_USERNAME |
user |
Outbound SMTP username for relay mode |
Optional |
MTA_OUT_RELAY_PASSWORD |
pass |
Outbound SMTP password for relay mode |
Optional |
MTA_OUT_DIRECT_PROXIES |
[] |
List of SOCKS proxy URLs (randomly chosen when non-empty; used in direct mode) |
Optional |
MTA_OUT_DIRECT_PORT |
25 |
TCP port for direct mode on remote MX servers |
Optional |
MTA_OUT_SMTP_TLS_SECURITY_LEVEL |
may |
SMTP TLS security level: none, may (opportunistic, no cert check, matches Postfix), or secure (mandatory TLS + CA chain + hostname check). Applied to both direct and relay modes — set to secure when running against a controlled relay with a valid cert. |
Optional |
MDA_API_SECRET |
my-shared-secret-mda |
Shared secret for MDA API |
Required |
MDA_API_BASE_URL |
http://backend-dev:8000/api/v1.0/ |
Base URL for MDA API |
Dev |
Email Domain Configuration
| Variable |
Default |
Description |
Required |
MESSAGES_ACCEPT_ALL_EMAILS |
False |
Accept emails to any domain |
Optional |
DKIM Configuration
| Variable |
Default |
Description |
Required |
MESSAGES_DKIM_SELECTOR |
default |
DKIM selector |
Optional |
MESSAGES_DKIM_DEFAULT_SELECTOR |
stmessages |
Default DKIM selector applied to managed domains that don't override it. |
Optional |
MESSAGES_DKIM_DOMAINS |
[] |
List of domains for DKIM signing |
Optional |
MESSAGES_DKIM_PRIVATE_KEY_B64 |
None |
Base64 encoded DKIM private key |
Optional |
MESSAGES_DKIM_PRIVATE_KEY_FILE |
None |
Path to DKIM private key file |
Optional |
MESSAGES_DKIM_VERIFY_OUTGOING |
False |
Verify the DKIM signature on outgoing messages before sending. |
Optional |
MESSAGES_SPF_CHECK_OUTGOING |
False |
Block outgoing messages when the sending domain's SPF includes are not correctly set up. |
Optional |
Storage Configuration
S3-Compatible Storage
| Variable |
Default |
Description |
Required |
AWS_S3_ENDPOINT_URL |
http://objectstorage:9000 |
S3 endpoint URL |
Optional |
AWS_S3_ACCESS_KEY_ID |
messages |
S3 access key |
Optional |
AWS_S3_SECRET_ACCESS_KEY |
password |
S3 secret key |
Optional |
AWS_S3_SIGNATURE_VERSION |
s3v4 |
S3 request signature version |
Optional |
AWS_S3_DOMAIN_REPLACE |
None |
If set, rewrites the host of generated S3 URLs to this value — e.g. map an internal endpoint to a public one for presigned/download links. |
Optional |
AWS_S3_REGION_NAME |
None |
S3 region |
Optional |
AWS_STORAGE_BUCKET_NAME |
st-messages-media-storage |
S3 bucket name |
Optional |
AWS_S3_UPLOAD_POLICY_EXPIRATION |
86400 |
Upload policy expiration (24h) |
Optional |
ITEM_FILE_MAX_SIZE |
5368709120 |
Max file size (5GB) |
Optional |
Message Imports Storage
| Variable |
Default |
Description |
Required |
STORAGE_MESSAGE_IMPORTS_ENDPOINT_URL |
http://objectstorage:9000 |
S3 endpoint URL |
Required |
STORAGE_MESSAGE_IMPORTS_BUCKET_NAME |
msg-imports |
S3 bucket name |
Required |
STORAGE_MESSAGE_IMPORTS_ACCESS_KEY |
st-messages |
S3 access key |
Required |
STORAGE_MESSAGE_IMPORTS_SECRET_KEY |
password |
S3 secret key |
Required |
STORAGE_MESSAGE_IMPORTS_REGION_NAME |
None |
S3 region |
Optional |
STORAGE_MESSAGE_IMPORTS_EXPIRE_POLICY |
3600 |
Upload policy expiration (1h) |
Optional |
Tiered Blob Storage
Blobs (raw email bodies and attachments) live in PostgreSQL by default and
can be offloaded to S3 after a configurable age. See tiered storage
docs for the runbook (rotation, verify, recovery).
The bucket is treated as "configured" when at least one of
STORAGE_MESSAGE_BLOBS_ENDPOINT_URL or STORAGE_MESSAGE_BLOBS_ACCESS_KEY
is set — the periodic offload task and the read-from-S3 path both
short-circuit otherwise. Bucket creds must additionally be valid for
the periodic task to actually move data; until both
MESSAGES_BLOBS_OFFLOAD_ENABLED=True and creds are in place, every
blob stays in PG.
| Variable |
Default |
Description |
Required |
STORAGE_MESSAGE_BLOBS_ENDPOINT_URL |
unset |
S3 endpoint URL for blob bucket |
Optional |
STORAGE_MESSAGE_BLOBS_BUCKET_NAME |
unset |
S3 bucket name (must be set together with STORAGE_MESSAGE_BLOBS_ENDPOINT_URL to enable offload) |
Optional |
STORAGE_MESSAGE_BLOBS_ACCESS_KEY |
unset |
S3 access key |
Optional |
STORAGE_MESSAGE_BLOBS_SECRET_KEY |
unset |
S3 secret key |
Optional |
STORAGE_MESSAGE_BLOBS_REGION_NAME |
unset |
S3 region |
Optional |
MESSAGES_BLOBS_OFFLOAD_ENABLED |
False |
Master switch for the periodic offload task. Hourly schedule with a 55-minute per-tick budget; processes blobs sequentially (no per-blob fan-out). The orphan-blob GC sweep (gc_orphan_blobs_task) runs on the same hourly cadence regardless of this flag — its job is reference-graph cleanup, not S3 offload. |
Optional |
MESSAGES_BLOBS_OFFLOAD_DELAY |
86400 |
Age threshold (seconds) for offload (0 = immediate) |
Optional |
MESSAGES_BLOBS_OFFLOAD_MIN_SIZE |
0 |
Minimum blob size in bytes (0 = all) |
Optional |
MESSAGES_BLOBS_COMPRESS |
zstd:7 |
Default compression: none, zstd, or zstd:<level> |
Optional |
MESSAGES_BLOBS_ENCRYPT_KEYS |
{} |
JSON dict mapping key_id → entry. Each entry must be {"algo": "aes-gcm", "secret": "<32+ chars>", "active": <bool>}. Add "active": true to exactly one entry to make it the key new blobs are encrypted with; entries without active (or with active=false) stay readable for legacy ciphertext. The secret is SHA-256'd to a 32-byte AEAD key, so its strength is whatever entropy the operator supplied — use openssl rand -base64 32 (or equivalent). Startup emits a warning when a secret is shorter than 32 characters; that floor is a length check only, not an entropy measurement. |
Optional |
MESSAGES_BLOBS_VERIFY_HASH |
False |
When True, Blob.get_content() re-hashes plaintext and rejects mismatches. One SHA-256 over the plaintext per read; main value is for key_id=0 blobs (encrypted blobs are already AAD-bound). |
Optional |
Mobile OTA Bundles
Public (anonymous read) bucket holding the mobile OTA artifacts, one
self-contained folder per release channel (channels/<channel>/manifest.json +
channels/<channel>/bundles/<version>.zip). Created with make mobile-ota-bucket;
bundles are published with make ota-publish [CHANNEL=…]. Channels are fully
independent: NEXT_PUBLIC_* vars are inlined into the bundle at build time, so
each channel ships its own build — never copy a bundle across channels.
Except for MOBILE_OTA_MANIFEST_URL (a backend setting served to the apps
through the /config endpoint), these are frontend-toolchain / publish-time
variables (read by src/frontend/scripts/*-ota*.mjs, not by Django). They live
in the frontend env files; in CI they come from secrets.
Opt-in in dev: they ship commented out in
deploy/env/frontend.defaults (the values below are the working
dev-stack ones) — uncomment them, along with MOBILE_OTA_MANIFEST_URL in
backend.local, to exercise the OTA chain locally.
Hot reload (MOBILE_DEV_SERVER_URL, on by default in dev) skips the startup
OTA check; disable it to test OTA end to end.
| Variable |
Default |
Description |
Required |
MOBILE_OTA_S3_ENDPOINT |
http://objectstorage:9000 |
S3 endpoint the script writes to (compose network in dev; target S3 in CI) |
Optional |
MOBILE_OTA_S3_BUCKET |
messages-ota |
S3 bucket name for OTA bundles |
Optional |
MOBILE_OTA_S3_ACCESS_KEY |
st-messages |
S3 access key |
Optional |
MOBILE_OTA_S3_SECRET_KEY |
password |
S3 secret key |
Optional |
MOBILE_OTA_S3_REGION |
us-east-1 |
S3 region |
Optional |
MOBILE_OTA_S3_KEY_PREFIX |
`` (empty) |
Object key prefix; empty for a dedicated bucket root, messages/mobileapp/ for a shared bucket. Must stay consistent with MOBILE_OTA_PUBLIC_BASE_URL |
Optional |
MOBILE_OTA_CHANNEL |
dev (dev env) |
Release channel mobile:ota:publish targets (channels/<channel>/…); overridable per run with --channel / make ota-publish CHANNEL=…. The deploy pipeline uses staging and prod, each publishing its own build. Must match the channel segment of the MOBILE_OTA_MANIFEST_URL served by the backend this deployment's apps talk to |
Optional |
MOBILE_OTA_MANIFEST_URL |
None (backend env) |
OTA channel manifest URL the apps poll at startup, served through the /config endpoint — so the followed channel can change without shipping a new native build. Must point to the channel this deployment publishes to. Unset disables OTA |
Optional |
MOBILE_OTA_PUBLIC_BASE_URL |
http://localhost:8906/messages-ota |
Device-reachable read base URL written into the manifest |
Optional |
MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 |
None |
Base64-encoded (single-line) RSA private-key PEM signing/encrypting each bundle at publish time (publish-ota.mjs). CI secret only — never commit. Unset ⇒ publish fails. See mobile.md |
Optional |
MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 |
None |
Base64-encoded (single-line) RSA public-key PEM baked into the app at cap sync time (capacitor.config.ts, native verification) and inlined by Vite into the JS bundle (ota.ts refuses a server-provided manifest URL on a key-less build). Read by the builds, not the publish scripts — an OTA-enabled build can never apply an unsigned bundle |
Required if OTA enabled |
MOBILE_OTA_BUILD_ID |
<count>-<sha> (git-derived) |
Hybrid release id stamped into the builtin bundle version at cap sync (capacitor.config.ts) and used as the default OTA VERSION. Computed by the Makefile from git; override in CI to pin a build. See mobile.md |
Optional |
Static Files
| Variable |
Default |
Description |
Required |
STORAGES_STATICFILES_BACKEND |
django.contrib.staticfiles.storage.StaticFilesStorage |
Static files storage backend |
Optional |
Authentication & Authorization
OIDC Configuration
| Variable |
Default |
Description |
Required |
OIDC_CREATE_USER |
False |
Automatically create users from OIDC |
Optional |
OIDC_RP_CLIENT_ID |
messages |
OIDC client ID |
Required |
OIDC_RP_CLIENT_SECRET |
ThisIsAnExampleKeyForDevPurposeOnly |
OIDC client secret |
Required |
OIDC_RP_SIGN_ALGO |
RS256 |
OIDC signing algorithm |
Optional |
OIDC_RP_SCOPES |
openid email |
OIDC scopes |
Optional |
OIDC_OP_JWKS_ENDPOINT |
http://keycloak:8000/realms/messages/protocol/openid-connect/certs |
OIDC JWKS endpoint |
Required |
OIDC_OP_AUTHORIZATION_ENDPOINT |
http://localhost:8902/realms/messages/protocol/openid-connect/auth |
OIDC authorization endpoint |
Required |
OIDC_OP_TOKEN_ENDPOINT |
http://keycloak:8000/realms/messages/protocol/openid-connect/token |
OIDC token endpoint |
Required |
OIDC_OP_USER_ENDPOINT |
http://keycloak:8000/realms/messages/protocol/openid-connect/userinfo |
OIDC user info endpoint |
Required |
OIDC_OP_LOGOUT_ENDPOINT |
None |
OIDC logout endpoint |
Optional |
OIDC_USERINFO_ESSENTIAL_CLAIMS |
[] |
Essential OIDC claims |
Optional |
OIDC_USERINFO_FULLNAME_FIELDS |
["first_name", "last_name"] |
Fields to use for full name |
Optional |
OIDC_STORE_ACCESS_TOKEN |
False |
Store access token |
Optional |
OIDC_STORE_REFRESH_TOKEN |
False |
Store refresh token |
Optional |
OIDC_STORE_REFRESH_TOKEN_KEY |
None |
Refresh token encryption key (Must be a valid Fernet key) |
Optional |
OIDC Advanced Settings
| Variable |
Default |
Description |
Required |
OIDC_USE_NONCE |
True |
Use nonce in OIDC flow |
Optional |
OIDC_REDIRECT_REQUIRE_HTTPS |
False |
Require HTTPS for redirects |
Optional |
OIDC_REDIRECT_ALLOWED_HOSTS |
["http://localhost:8902", "http://localhost:8900"] |
Allowed redirect hosts |
Optional |
OIDC_STORE_ID_TOKEN |
True |
Store ID token |
Optional |
OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION |
True |
Use email as fallback identifier |
Optional |
OIDC_ALLOW_DUPLICATE_EMAILS |
False |
Allow duplicate emails (⚠️ Security risk) |
Optional |
OIDC_AUTH_REQUEST_EXTRA_PARAMS |
{"acr_values": "eidas1"} |
Extra parameters for auth requests |
Optional |
OIDC_AUTH_REQUEST_FORWARDED_PARAMS |
["login_hint"] |
Forwarded parameters for auth requests |
Optional |
User Mapping (⚠️ DEPRECATED)
Those settings are deprecated and will be removed in the future.
| Variable |
Default |
Description |
Required |
⚠️ Deprecated |
USER_OIDC_ESSENTIAL_CLAIMS |
[] |
Essential OIDC claims |
Optional |
Renamed to OIDC_USERINFO_ESSENTIAL_CLAIMS |
USER_OIDC_FIELDS_TO_FULLNAME |
["first_name", "last_name"] |
Fields for full name |
Optional |
Renamed to OIDC_USERINFO_FULLNAME_FIELDS |
USER_OIDC_FIELD_TO_SHORTNAME |
first_name |
Field for short name |
Optional |
Unused, will be removed in the future |
Authentication URLs
| Variable |
Default |
Description |
Required |
LOGIN_REDIRECT_URL |
http://localhost:8900 |
Post-login redirect URL |
Optional |
LOGIN_REDIRECT_URL_FAILURE |
http://localhost:8900 |
Login failure redirect URL |
Optional |
LOGOUT_REDIRECT_URL |
http://localhost:8900 |
Post-logout redirect URL |
Optional |
ALLOW_LOGOUT_GET_METHOD |
True |
Allow GET method for logout |
Optional |
Mobile App Build (Capacitor)
| Variable |
Default |
Description |
Required |
MOBILE_APP_ID |
local.suitenumerique.messages |
Store/OS bundle identifier of the native app. The repo ships a neutral placeholder; an organisation publishing to the App Store / Play Store overrides it with its own signed id. Read by cap sync (container) and the native builds — gradle applicationId, iOS PRODUCT_BUNDLE_IDENTIFIER — so it must be exported in both the container env and the host/CI env. Independent of the auth callback scheme (stmessages). |
Optional |
MOBILE_DEV_SERVER_URL |
http://localhost:8900 (dev env, frontend.defaults) |
Dev only. URL of the Vite dev server baked as Capacitor server.url at cap sync (capacitor.config.ts): the WebView then loads the app from Vite with hot reload instead of the embedded bundle. To disable (embedded bundle / OTA testing), set it empty in frontend.local and rerun make mobile-build. Must never be set for a release build — a gradle guard fails Android release builds carrying it. See mobile.md |
Optional |
MOBILE_ALLOW_CLEARTEXT_FOR_DEV |
1 (dev env, frontend.defaults) |
Dev only. Baked as Capacitor server.cleartext at cap sync (capacitor.config.ts), i.e. android:usesCleartextTraffic in the Android manifest: allows plain HTTP for the whole app — the WebView reaching the Vite dev server and the native fetch/OTA layer reaching the http://localhost:8901 backend and the RustFS OTA bucket (needed even with hot reload disabled). Must never be set for a release build — the manifest then stays cleartext-free. iOS equivalent: NSAllowsLocalNetworking (Info.plist, manual). |
Optional |
Mobile App Authentication (Capacitor)
| Variable |
Default |
Description |
Required |
MOBILE_AUTH_CALLBACK_SCHEMES |
[] |
JSON list of deep-link schemes the OIDC callback may redirect to after a mobile-initiated login (e.g. ["stmessages"]). An empty list disables the mobile session handoff. |
Optional |
MOBILE_AUTH_TOKEN_TTL |
60 |
Lifetime (seconds) of the one-time token a mobile app exchanges for its session cookie on /api/v1.0/mobile/auth/exchange/ |
Optional |
API_MOBILE_AUTH_EXCHANGE_THROTTLE_RATE |
10/minute |
Per-IP rate limit on the anonymous /api/v1.0/mobile/auth/exchange/ endpoint. A legitimate login exchanges once; the cap only slows down brute-force guessing of the one-time token |
Optional |
Security & CORS
| Variable |
Default |
Description |
Required |
CORS_ALLOW_ALL_ORIGINS |
True |
Allow all CORS origins |
Optional |
CORS_ALLOWED_ORIGINS |
[] |
Specific allowed CORS origins |
Optional |
CORS_ALLOWED_ORIGIN_REGEXES |
[] |
Regex patterns for allowed origins |
Optional |
CSRF_TRUSTED_ORIGINS |
["http://localhost:8900", "http://localhost:8901"] |
Trusted origins for CSRF |
Optional |
ALLOWED_HOSTS |
[] |
Django host/domain allow-list setting (settings.ALLOWED_HOSTS). In the Base/Production configurations it is populated from the DJANGO_ALLOWED_HOSTS env var (see above); the Development configuration hardcodes ["*"]. The bucket_cors management command also reads it to build S3 CORS origins. |
Optional |
SERVER_TO_SERVER_API_TOKENS |
[] |
API tokens for server-to-server auth |
Optional |
SSRF_ALLOWED_HOSTS |
[] |
Comma-separated list of exact, case-insensitive hostnames that bypass the SSRF private/internal-IP checks (webhook URLs, image proxy, IMAP, CalDAV). Use only for trusted destinations that resolve to a private address from inside the platform network (e.g. app-to-app traffic on an internal overlay). Each entry is a deliberate hole in the SSRF protection — keep the list as narrow as possible. |
Optional |
SALT_KEY |
[] |
Key(s) for Django Fernet-encrypted model fields. Accepts a list for rotation (["new_key", "old_key"]); the first is used to encrypt, all are tried to decrypt. |
Optional |
Monitoring & Observability
Sentry
| Variable |
Default |
Description |
Required |
SENTRY_DSN |
None |
Sentry DSN for error tracking, shared with the frontend through the /config endpoint |
Optional |
NEXT_PUBLIC_SENTRY_DSN |
None |
Deprecated (build-time fallback, will be removed) — use SENTRY_DSN |
Optional |
NEXT_PUBLIC_SENTRY_ENVIRONMENT |
None |
Deprecated (build-time fallback, will be removed) — the frontend now uses the backend ENVIRONMENT |
Optional |
Selfcheck
End-to-end mail delivery probe — see selfcheck.md for details.
| Variable |
Default |
Description |
Required |
MESSAGES_SELFCHECK_FROM |
None |
Email address the selfcheck sends from. Leave unset to disable the selfcheck. |
Optional |
MESSAGES_SELFCHECK_TO |
None |
Email address the selfcheck sends to. Leave unset to disable the selfcheck. |
Optional |
MESSAGES_SELFCHECK_SECRET |
self-check-secret-for-dev |
Secret string embedded in the test message body |
Optional |
MESSAGES_SELFCHECK_INTERVAL |
600 |
Interval between selfcheck runs, in seconds |
Optional |
MESSAGES_SELFCHECK_TIMEOUT |
60 |
Timeout for message reception, in seconds |
Optional |
MESSAGES_SELFCHECK_WEBHOOK_URL |
None |
Webhook URL POSTed on each successful selfcheck (updown.io-compatible heartbeat) |
Optional |
MESSAGES_SELFCHECK_SENTRY_MONITOR_SLUG |
None |
Sentry cron monitor slug. When set (with SENTRY_DSN), each run is reported as a Sentry check-in. |
Optional |
Logging
| Variable |
Default |
Description |
Required |
LOGGING_LEVEL_LOGGERS_ROOT |
INFO |
Root logger level |
Optional |
LOGGING_LEVEL_LOGGERS_APP |
INFO |
Application logger level |
Optional |
LOGGING_LEVEL_HANDLERS_CONSOLE |
INFO |
Console handler level |
Optional |
Prometheus
| Variable |
Default |
Description |
Required |
ENABLE_PROMETHEUS |
False |
Enable Prometheus monitoring |
Optional |
PROMETHEUS_API_KEY |
None |
Bearer token required to access metrics. If unset, the endpoint is public. Set this in production. |
Optional |
OpenAPI Schema
| Variable |
Default |
Description |
Required |
SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK |
False |
Enable deploy check in OpenAPI |
Optional |
Frontend Configuration
The frontend is configured at runtime through the backend /api/v1.0/config/ endpoint (see the backend Frontend settings section). The only build-time variable left is the API origin, needed to reach that endpoint.
| Variable |
Default |
Description |
Required |
NEXT_PUBLIC_API_ORIGIN |
http://localhost:8901 |
Frontend API origin |
Dev |
The following build-time variables are deprecated: they only act as fallbacks when the backend does not provide the corresponding setting, and will be removed in a future release.
| Deprecated variable |
Replaced by (backend setting) |
NEXT_PUBLIC_LANGUAGES |
LANGUAGES |
NEXT_PUBLIC_DEFAULT_LANGUAGE |
LANGUAGE_CODE |
NEXT_PUBLIC_FORCED_DEFAULT_LANGUAGE |
FRONTEND_FORCED_DEFAULT_LANGUAGE |
NEXT_PUBLIC_THEME_CONFIG |
FRONTEND_THEME_CONFIG |
NEXT_PUBLIC_MULTIPART_UPLOAD_CHUNK_SIZE |
FRONTEND_MULTIPART_UPLOAD_CHUNK_SIZE_MB |
NEXT_PUBLIC_FEEDBACK_WIDGET_API_URL |
FRONTEND_FEEDBACK_WIDGET_CONFIG (api_url key) |
NEXT_PUBLIC_FEEDBACK_WIDGET_PATH |
FRONTEND_FEEDBACK_WIDGET_CONFIG (path key) |
NEXT_PUBLIC_FEEDBACK_WIDGET_CHANNEL |
FRONTEND_FEEDBACK_WIDGET_CONFIG (channel key) |
NEXT_PUBLIC_FEEDBACK_WIDGET_HOME_CHANNEL |
FRONTEND_FEEDBACK_WIDGET_CONFIG (home_channel key) |
NEXT_PUBLIC_HELP_CENTER_URL |
FRONTEND_HELP_CENTER_URL |
NEXT_PUBLIC_LAGAUFRE_WIDGET_API_URL |
FRONTEND_LAGAUFRE_WIDGET_CONFIG (api_url key) |
NEXT_PUBLIC_LAGAUFRE_WIDGET_PATH |
FRONTEND_LAGAUFRE_WIDGET_CONFIG (path key) |
NEXT_PUBLIC_SENTRY_DSN |
SENTRY_DSN |
NEXT_PUBLIC_SENTRY_ENVIRONMENT |
ENVIRONMENT (backend environment) |
NEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL |
MOBILE_OTA_MANIFEST_URL |
Development Tools
Crowdin (Translations)
| Variable |
Default |
Description |
Required |
CROWDIN_PERSONAL_TOKEN |
Your-Personal-Token |
Crowdin API token |
Dev |
CROWDIN_PROJECT_ID |
Your-Project-Id |
Crowdin project ID |
Dev |
CROWDIN_BASE_PATH |
/app/src |
Base path for translations |
Dev |
Application Settings
Common Feature Flags
Kill-switches for opt-out features. Flip to False to disable the
corresponding API action and hide the related frontend entry points,
without redeploying the frontend (the flag is pulled from
GET /api/v1.0/config/).
| Variable |
Default |
Description |
Required |
FEATURE_IMPORT_MESSAGES |
True |
Enables message import (IMAP, PST, MBOX, etc.). When False, mailbox admins lose the CAN_IMPORT_MESSAGES ability. |
Optional |
FEATURE_MAILBOX_ADMIN_CHANNELS |
api_key,webhook |
Comma-separated list of channel types enabled for mailbox admin (e.g., widget,api_key). Empty list disables all channel types. |
Optional |
FEATURE_MAILDOMAIN_CREATE |
True |
Allows superusers to create new mail domains via the API. When False, the create action returns 403. |
Optional |
FEATURE_MAILDOMAIN_MANAGE_ACCESSES |
True |
Allows managing mail domain accesses (create/delete). When False, those actions return 403. |
Optional |
FEATURE_MESSAGE_TEMPLATES |
True |
Enables the "message templates" feature. When False, mailbox admins lose the CAN_MANAGE_MESSAGE_TEMPLATES ability and the related UI is hidden. |
Optional |
FEATURE_THREAD_SPLIT |
True |
Enables "split thread" feature. When False, the split API action returns 404 and the frontend hides the menu entry. |
Optional |
FEATURE_MAILDOMAIN_MANAGE_TOTP |
False |
Enables the "Mandatory 2FA" (TOTP) toggle for mail domains. Requires the Keycloak identity-provider settings and KEYCLOAK_TOTP_ROLE_ID. |
Optional |
Business Logic
| Variable |
Default |
Description |
Required |
TRASHBIN_CUTOFF_DAYS |
30 |
Days an item may sit in the trashbin (trashed or spam messages) before the nightly cleanup_trashbin_task permanently deletes it. Age is measured from when the message entered the bin (trashed_at), falling back to created_at for rows written before that was recorded. Set to 0 to disable automatic deletion entirely. |
Optional |
TRASHBIN_ALLOW_EMPTY |
admins |
Who may manually empty a trashbin folder from the UI, an irreversible bulk delete: never (only the nightly sweep deletes), admins (mailbox role ADMIN), or editors (role EDITOR and above). Any other value is rejected at startup. |
Optional |
INVITATION_VALIDITY_DURATION |
604800 |
Invitation validity (7 days) |
Optional |
MESSAGES_MANUAL_RETRY_MAX_AGE |
604800 |
Maximum age in seconds for a message to be eligible for manual retry of failed deliveries (7 days) |
Optional |
MESSAGES_INBOUND_DEFERRAL_MAX_AGE |
172800 |
Maximum age in seconds an inbound message is deferred (retried every 5 min) when a processing step keeps failing, before the pipeline delivers it anyway (recorded as postmark["processing"]) rather than holding it indefinitely (48 hours) |
Optional |
MAX_INCOMING_EMAIL_SIZE |
10485760 |
Maximum size in bytes for incoming email (including attachments and body) (10MB) |
Optional |
MAX_OUTGOING_ATTACHMENT_SIZE |
20971520 |
Maximum size in bytes for outgoing email attachments (20MB) |
Optional |
MAX_OUTGOING_BODY_SIZE |
5242880 |
Maximum size in bytes for outgoing email body (text + HTML) (5MB) |
Optional |
MAX_TEMPLATE_IMAGE_SIZE |
2097152 |
Maximum size in bytes for images embedded in templates and signatures (2MB) |
Optional |
MAX_RECIPIENTS_PER_MESSAGE |
500 |
Maximum number of recipients per message (to + cc + bcc) |
Optional |
MAX_THREAD_EVENT_EDIT_DELAY |
3600 |
Time window in seconds during which a ThreadEvent (internal comment) can still be edited or deleted after creation. Set to 0 to disable the restriction. |
Optional |
MESSAGES_ALLOW_INTERNAL_DELIVERY |
True |
Deliver mailbox-to-mailbox mail through the internal inbound pipeline (fast path). Set False to force same-instance mail out through the external MTA so it passes the same scanning/archiving as outbound. |
Optional |
MESSAGES_MAILBOX_LOCALPART_DENYLIST_PERSONAL |
[] |
Local parts rejected for personal mailboxes (case-insensitive exact match). |
Optional |
Model custom attributes schema
Note: Custom attributes are stored in a JSONField (Take a look at User and MailDomain models).
| Variable |
Default |
Description |
Required |
SCHEMA_CUSTOM_ATTRIBUTES_USER |
{} |
JSONSchema definition of the User custom attributes |
Optional |
SCHEMA_CUSTOM_ATTRIBUTES_MAILDOMAIN |
{} |
JSONSchema definition of the MailDomain custom attributes |
Optional |
Internationalization
| Variable |
Default |
Description |
Required |
LANGUAGE_CODE |
en-us |
Default backend language code |
Optional |
AI
| Variable |
Default |
Description |
Required |
AI_BASE_URL |
None |
Default URL to access AI API endpoint (Albert API) |
Optional |
AI_API_KEY |
None |
API Key used for AI features |
Optional |
AI_MODEL |
None |
Default model used for AI features |
Optional |
FEATURE_AI_SUMMARY |
False |
Default enabled mode for summary AI features |
Required |
FEATURE_AI_AUTOLABELS |
False |
Default enabled mode for label AI features |
Required |
Push Notifications
Push notifications for new messages, delivered to iOS (APNs), Android (FCM) and web browsers (Web Push / VAPID). The feature is off by default (PUSH_ENABLED=False): no tokens are pushed to and no external gateway is contacted. To go live, set PUSH_ENABLED=True and fully configure at least one gateway below.
Each gateway is all-or-nothing, and validated at boot: with PUSH_ENABLED=True, each gateway's variables must be set together or not at all (e.g. PUSH_VAPID_PRIVATE_KEY, PUSH_VAPID_PUBLIC_KEY and PUSH_VAPID_SUBJECT for Web Push), otherwise Django raises a ValueError at startup — a partial group would enroll devices that never receive anything. Device registration additionally refuses (400) any platform whose gateway is absent altogether, and a sender that finds itself without credentials at send time (removed after devices enrolled) drops with a deduplicated warning rather than silently. A deployment configuring only some gateways (e.g. native-only, no VAPID vars) boots and runs fine.
| Variable |
Default |
Description |
Required |
PUSH_ENABLED |
False |
Master switch. When False, the feature is fully dark: no gateway is contacted and the enqueue helper never schedules the Celery task. |
Optional |
PUSH_APNS_KEY |
None |
Contents of the APNs auth key .p8 file (PEM). Required for iOS (with the three vars below). |
Optional |
PUSH_APNS_KEY_ID |
None |
APNs auth key id (Key ID from the Apple developer portal). |
Optional |
PUSH_APNS_TEAM_ID |
None |
Apple developer Team ID. |
Optional |
PUSH_APNS_BUNDLE_ID |
None |
App bundle id, used as the APNs topic. |
Optional |
PUSH_APNS_USE_SANDBOX |
False |
False targets Apple's production gateway; True the sandbox gateway (only accepts tokens from a development-signed build). |
Optional |
PUSH_FCM_CREDENTIALS |
None |
Firebase service-account JSON (the whole file contents) as a string. Required for Android (with PUSH_FCM_PROJECT_ID). |
Optional |
PUSH_FCM_PROJECT_ID |
None |
Firebase project id; selects the FCM HTTP v1 endpoint. Separate staging from production by pointing at a different Firebase project, not a flag. |
Optional |
PUSH_VAPID_PRIVATE_KEY |
None |
VAPID application-server private key (PEM or base64url). Required for Web Push (see boot coupling above). Rotating it orphans every existing web subscription — clients must re-subscribe — and requires re-deriving the public key. |
Optional |
PUSH_VAPID_PUBLIC_KEY |
None |
Matching VAPID public key (base64url, the uncompressed P-256 point). Served verbatim via /config as the browser's applicationServerKey; public by definition. Derive it from the private key with python manage.py derive_vapid_public_key (add --verify to check the pair matches). |
Optional |
PUSH_VAPID_SUBJECT |
None |
VAPID sub claim; must be a mailto: or https: URI, e.g. mailto:ops@example.com. |
Optional |
PUSH_MAX_DEVICES_PER_USER |
20 |
Hard ceiling on push devices one user may keep. Registering beyond it prunes the least-recently-used device(s). |
Optional |
Throttling
Outbound message throttling limits the number of external recipients (recipients whose domain is not managed by this instance) that can be sent from a mailbox or maildomain within a time period, using simple fixed time windows.
| Variable |
Default |
Description |
Required |
THROTTLE_MAILBOX_OUTBOUND_EXTERNAL_RECIPIENTS |
None |
Rate limit per mailbox. Format: count/period where period is minute, hour, or day. Example: 1000/day limits each mailbox to 1000 external recipients per day. |
Optional |
THROTTLE_MAILDOMAIN_OUTBOUND_EXTERNAL_RECIPIENTS |
None |
Rate limit per maildomain. Format: count/period. Example: 10000/day limits each domain to 10000 external recipients per day. |
Optional |
THROTTLE_AUTOREPLY_PER_SENDER |
1/day |
Rate limit for autoreplies per sender per mailbox. Format: count/period. Example: 1/day limits each sender to 1 autoreply per day per mailbox. |
Optional |
API_USERS_LIST_THROTTLE_RATE_SUSTAINED |
180/hour |
Sustained rate limit on the users-list API (per user). |
Optional |
API_USERS_LIST_THROTTLE_RATE_BURST |
30/minute |
Burst rate limit on the users-list API (per user). |
Optional |
API_CALDAV_CONFLICTS_THROTTLE_RATE |
30/minute |
Rate limit on the CalDAV conflict-check API. |
Optional |
API_WIDGET_INBOUND_CHANNEL_THROTTLE_RATE |
30/minute |
Rate limit on inbound widget submissions, per widget channel. |
Optional |
API_WIDGET_INBOUND_IP_THROTTLE_RATE |
10/minute |
Per-IP burst limit on inbound widget submissions. |
Optional |
API_DEVICE_REGISTRATION_THROTTLE_RATE |
30/hour |
Per-user rate limit on push device (re)registration. Clients re-register on every cold launch and on token rotation, so this is deliberately loose; the hard ceiling on distinct devices is PUSH_MAX_DEVICES_PER_USER. |
Optional |
Image Proxy
Note: By default IMAGE_PROXY_MAX_SIZE is set to 5MB. We do not encourage to increase this value as
it can lead to memory exhaustion, increase at your own risk.
| Variable |
Default |
Description |
Required |
IMAGE_PROXY_ENABLED |
False |
Whether external images should be proxied |
Optional |
IMAGE_PROXY_MAX_SIZE |
5242880 (5MB) |
Maximum size in bytes for external images |
Optional |
IMAGE_PROXY_CACHE_TTL |
2592000 (30 days) |
Cache TTL in seconds for external images |
Optional |
MESSAGE_TRUSTED_LINK_DOMAINS |
[] |
Comma-separated list of hostnames whose external links open without the redirect confirmation modal. A lone * trusts every host (disables the modal). A leading *. wildcard also matches subdomains (*.gouv.fr matches gouv.fr and impots.gouv.fr); any other entry matches the host exactly (case-insensitive). Masked links (display text pointing to a different host than the real target) always prompt, even when their target is listed here. |
Optional |
Frontend
These settings are unset by default: an unset setting is omitted from the /config payload and the frontend then falls back on its deprecated NEXT_PUBLIC_* build-time variable (if any), then on its built-in default. Setting a value here always takes precedence over the frontend fallbacks.
| Variable |
Default |
Description |
Required |
FRONTEND_SILENT_LOGIN_ENABLED |
False |
Whether silent login is enabled |
Optional |
FRONTEND_THEME_CONFIG |
None (frontend defaults to {"theme": "white-label"}) |
Theme configuration served to the frontend (theme, terms_of_service_url, footer), as JSON |
Optional |
FRONTEND_FORCED_DEFAULT_LANGUAGE |
None (frontend defaults to False) |
When True, the frontend default language fallback is LANGUAGE_CODE instead of the browser language |
Optional |
FRONTEND_MULTIPART_UPLOAD_CHUNK_SIZE_MB |
None (frontend defaults to 100) |
Chunk size in MB for frontend multipart uploads |
Optional |
FRONTEND_HELP_CENTER_URL |
None |
Help center URL |
Optional |
FRONTEND_FEEDBACK_WIDGET_CONFIG |
None |
Feedback widget configuration (api_url, path, channel, home_channel), as JSON |
Optional |
FRONTEND_LAGAUFRE_WIDGET_CONFIG |
None |
Lagaufre widget configuration (api_url, path), as JSON |
Optional |
Note: every language listed in LANGUAGES must have its translation files in the frontend (/locales/*/xx-XX.json), otherwise the UI falls back to en-US.
Third-party Services
Drive
| Variable |
Default |
Description |
Required |
DRIVE_BASE_URL |
None |
Base URL to access Drive endpoints |
Optional |
DRIVE_APP_NAME |
Drive |
Name of the Drive application used in the frontend |
Optional |
Identity Provider (Keycloak)
Used for provisioning-side operations against Keycloak (e.g. toggling
mandatory 2FA on a mail domain). Distinct from the OIDC login settings
above, which handle end-user authentication.
| Variable |
Default |
Description |
Required |
IDENTITY_PROVIDER |
None |
Identity-provider integration to enable (e.g. keycloak). Unset disables provisioning-side IdP calls. |
Optional |
KEYCLOAK_URL |
None |
Base URL of the Keycloak server. |
Optional |
KEYCLOAK_REALM |
None |
Keycloak realm. |
Optional |
KEYCLOAK_CLIENT_ID |
None |
Service-account client id used for admin/provisioning calls. |
Optional |
KEYCLOAK_CLIENT_SECRET |
None |
Service-account client secret. |
Optional |
KEYCLOAK_GROUP_PATH_PREFIX |
None |
Prefix for Keycloak group paths mapped to mail domains. |
Optional |
KEYCLOAK_TOTP_ROLE_ID |
None |
Realm role id assigned in Keycloak when "Mandatory 2FA" is enabled for a mailbox (see FEATURE_MAILDOMAIN_MANAGE_TOTP). |
Optional |
Domain DNS Provisioning
Hosting of MX/SPF/DKIM records for managed mail domains, optionally
automated through a DNS provider.
| Variable |
Default |
Description |
Required |
MESSAGES_TECHNICAL_DOMAIN |
localhost |
Technical domain that MX/SPF/DKIM records point at. |
Optional |
MESSAGES_DNS_RECORDS |
MX/SPF/DKIM template |
JSON template of expected DNS records, with {technical_domain} placeholders. |
Optional |
DNS_DEFAULT_PROVIDER |
None |
DNS provider used to auto-create records (e.g. scaleway). Unset = manual DNS. |
Optional |
DNS_SCALEWAY_API_TOKEN |
None |
Scaleway API token (when DNS_DEFAULT_PROVIDER=scaleway). |
Optional |
DNS_SCALEWAY_PROJECT_ID |
None |
Scaleway project id. |
Optional |
DNS_SCALEWAY_TTL |
3600 |
TTL (seconds) for records created via Scaleway. |
Optional |
Calendar (CalDAV)
| Variable |
Default |
Description |
Required |
CALDAV_DEFAULT_URL |
None |
Base URL of the external CalDAV server. |
Optional |
CALDAV_DEFAULT_WEB_URL |
None |
URL of the calendar web UI surfaced to the frontend. |
Optional |
CALDAV_DEFAULT_PASSWORD |
None |
Credential for the default CalDAV account. |
Optional |
Entitlements
Pluggable backend deciding what a user/domain is entitled to.
| Variable |
Default |
Description |
Required |
ENTITLEMENTS_BACKEND |
core.entitlements.backends.local.LocalEntitlementsBackend |
Dotted path to the entitlements backend class. |
Optional |
ENTITLEMENTS_BACKEND_PARAMETERS |
{} |
JSON parameters passed to the backend. |
Optional |
ENTITLEMENTS_CACHE_TIMEOUT |
300 |
Cache TTL (seconds) for entitlement lookups. |
Optional |
STORAGE_USAGE_CACHE_TTL |
60 |
Cache TTL (seconds) for the computed storage usage of a mailbox or organization, as read by the quota gauge and the entitlements backends. The computation runs several correlated subqueries (~100 ms on a large mailbox), so it is not recomputed on every sidebar load. Emptying a trashbin and the nightly sweep both invalidate the affected mailboxes immediately; every other change (new mail, sends) shows up within this TTL. Set to 0 to always compute live. The /metrics endpoints ignore this and always compute live. |
Optional |
Message Import
Tuning for the resumable message importer (EML / MBOX / PST files and IMAP;
see also FEATURE_IMPORT_MESSAGES). Imports are modelled as type=import
channels and resumed from a Redis watermark by run_import_task. See
docs/imports.md for the design.
| Variable |
Default |
Description |
Required |
MESSAGES_IMPORT_IMAP_TIMEOUT |
60 |
Socket timeout (seconds) for IMAP import connections — applied to the connect and to each subsequent command (login, SEARCH, FETCH). Tripping it surfaces as a transient error the run resumes from. Replaces IMAP_TIMEOUT. |
Optional |
MESSAGES_IMPORT_STALL_TIMEOUT |
900 |
Seconds of heartbeat silence after which the scheduler treats a running import as crashed and re-dispatches it (which resumes from its watermark). Also the run-lock TTL. Must comfortably exceed the worst-case time between progress flushes. Continuous IMAP channels use MESSAGES_IMPORT_IMAP_POLL_INTERVAL as their clock instead. |
Optional |
MESSAGES_IMPORT_IMAP_POLL_INTERVAL |
900 |
Poll cadence (seconds) for continuous IMAP imports: the scheduler re-dispatches each active continuous channel this often to pull new mail. Global (not settable per import); must be a positive integer. |
Optional |
MESSAGES_IMPORT_MAX_FILE_SIZE |
53687091200 |
Largest archive (bytes) an import will accept (50 GiB); checked before a worker is spent on it. 0 disables the cap. |
Optional |
Spam Filtering
| Variable |
Default |
Description |
Required |
SPAM_CONFIG |
{} |
JSON config for the spam checker. Empty {} disables it. Example: {"rspamd_url": "http://mpa:8010/_api", "rspamd_auth": ""}. |
Optional |
Celery / Task Queue
| Variable |
Default |
Description |
Required |
DISABLE_CELERY_BEAT_SCHEDULE |
False |
Disable the periodic Beat schedule (search indexing, offload, selfcheck, …). |
Optional |
CELERY_TASK_SEND_SENT_EVENT |
True |
Emit Celery task-sent events (monitoring/Flower). |
Optional |
CELERY_WORKER_SEND_TASK_EVENTS |
True |
Workers emit task events (monitoring/Flower). |
Optional |
Metrics
| Variable |
Default |
Description |
Required |
METRICS_STORAGE_USED_OVERHEAD_BY_MESSAGE |
1024 |
Per-message overhead (bytes) added when computing reported storage usage. |
Optional |
Deprecated
Read by nothing any more. Unset them: a stale value silently does nothing.
| Variable |
Default |
Description |
Required |
⚠️ Deprecated |
PROVISIONING_API_KEY |
None |
Ignored since global api_key Channels landed. Migrate to a global api_key Channel. |
Optional |
Removed in a future release |
METRICS_API_KEY |
None |
Ignored since global api_key Channels landed. Migrate to a global api_key Channel. |
Optional |
Removed in a future release |
MESSAGES_TESTDOMAIN |
None |
The TESTDOMAIN feature (catch-all domain, email mapping, auto-creation) has been removed. Use a MailDomain with oidc_autojoin=True instead. Logs a [REMOVED] warning at startup while still set. |
Optional |
Removed |
MESSAGES_TESTDOMAIN_MAPPING_BASEDOMAIN |
None |
Same as above. |
Optional |
Removed |
IMAP_TIMEOUT |
None |
Renamed — use MESSAGES_IMPORT_IMAP_TIMEOUT. No startup warning: a value left here is ignored silently. |
Optional |
Removed |
IMAP_MAX_RETRIES |
None |
Removed with the resumable importer: a failed run now resumes from its watermark instead of burning a retry budget. No startup warning. |
Optional |
Removed |
Legend
- Required: Must be set for the application to function
- Dev: Required for development/testing environments
- Optional: Has sensible defaults, can be customized
Environment Files
The application uses environment files located in deploy/env/ for different services:
backend.defaults - Main Django application settings
common.defaults - Shared settings across services
frontend.defaults - Frontend configuration
postgresql.defaults - PostgreSQL database configuration
keycloak.defaults - Keycloak configuration
mta-in.defaults - Inbound mail server settings
mta-out.defaults - Outbound mail server settings
crowdin.defaults - Translation service configuration
Local Overrides
The make bootstrap command creates empty .local files for each service with a comment header:
These files are gitignored and allow for local development customizations without affecting the repository.
Security Notes
⚠️ Important Security Considerations:
- Never commit actual secrets - Use
.local files only
- OIDC_ALLOW_DUPLICATE_EMAILS - Should remain
False in production
- CORS_ALLOW_ALL_ORIGINS - Should be
False in production
- DJANGO_SECRET_KEY - Must be unique and secret in production
- Database passwords - Use strong, unique passwords
- API tokens - Rotate regularly and keep secure
Production Deployment
For production deployments, ensure:
- All Required variables are properly configured
- Secrets are managed through secure secret management systems
- HTTPS is enforced for all external communications
- Database connections use SSL/TLS
- File storage uses appropriate access controls