# Environment variables reference

> Reference: Required and optional service environment variables, defaults, secrets, queue names, ports, and provider-specific keys.

- Repository: macro-inc/macro
- GitHub: https://github.com/macro-inc/macro
- Human docs: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e
- Complete Markdown: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e/llms-full.txt

## Source Files

- `RUNNING_LOCALLY.md`
- `rust/cloud-storage/document_storage_service/src/config.rs`
- `rust/cloud-storage/document_cognition_service/src/config.rs`
- `rust/cloud-storage/authentication_service/src/config.rs`
- `rust/cloud-storage/email_service/src/config.rs`
- `rust/cloud-storage/notification_service/src/config.rs`
- `rust/cloud-storage/search_service/src/config.rs`
- `rust/cloud-storage/macro_env/src/lib.rs`

---

---
title: "Environment variables reference"
description: "Reference: Required and optional service environment variables, defaults, secrets, queue names, ports, and provider-specific keys."
---

Macro’s local and deployed service binaries read configuration directly from environment variables at startup. Most Rust services default `PORT` to `8080`, derive `ENVIRONMENT` with `prod` fallback, and fail fast when required variables are missing or cannot be parsed.

## Runtime conventions

| Convention | Behavior |
| --- | --- |
| `ENVIRONMENT` | Accepted values are `prod`, `dev`, and `local`. Missing or invalid values fall back to production behavior. |
| `env_var!` names | Rust `CamelCase` config structs map to `UPPER_SNAKE_CASE`; for example `DocumentStorageBucket` reads `DOCUMENT_STORAGE_BUCKET`. |
| Required variables | `env_var!` and direct `std::env::var(...).context(...)` reads fail service startup when absent. |
| Optional variables | `maybe_env_var!` and `.ok()` reads return `None` when absent. |
| Local secrets | In `ENVIRONMENT=local`, secret variables are normally used as literal secret values. |
| Deployed secrets | In `dev` and `prod`, variables passed through `LocalOrRemoteSecret` or explicit secret-manager lookups are treated as AWS Secrets Manager secret IDs. |
| AWS local mode | `LOCAL_AWS_URL` switches shared AWS clients to LocalStack-style endpoint and test credentials. |
| CORS override | `ALLOWED_ORIGINS` can replace the default comma-separated origin allowlist used by the shared CORS layer. |

<Warning>
Some numeric variables use `.unwrap()` after parsing. Invalid values can panic instead of returning a structured startup error.
</Warning>

## Local environment sources

Local setup expects an encrypted dotenv source and writes a runtime `.env` file.

```bash
just setup
just run_local
```

`just setup` decrypts `.env-local*.enc`, creates shared Docker networks and volumes, starts LocalStack, initializes databases, sets up FusionAuth, and builds local service images. `just run_local` patches local FusionAuth values into `.env` before starting Docker Compose.

Local E2E uses explicit overrides:

```bash
COMPOSE_FILE=docker-compose.yml:docker-compose.local-e2e.yml just run_local -d --wait
```

The local E2E override forces local Postgres, Redis, LocalStack, S3 buckets, DynamoDB tables, and queue names instead of shared dev assets.

## Ports

### Local service host ports

| Service | Host port | Container port / default |
| --- | ---: | ---: |
| `authentication-service` | `8080` | `8080` |
| `connection_gateway` | `8082` | `8080` |
| `contacts_service` | `8083` | `8080` |
| `document_cognition_service` | `8085` | `8080` |
| `document_storage_service` | `8086` | `8080` |
| `email_service` | `8087` | `8080` |
| `notification_service` | `8089` | `8080` |
| `search_processing_service` | `8092` | `8080` |
| `static_file_service` | `8094` | `8080` |
| `unfurl_service` | `8095` | `8080` |
| `lexical_service` | `8096` | `8096` |
| `image_proxy_service` | `8097` | `8080` |
| `static_file_cdn` | `8100` | `80` |
| `websocket_service` | `6969` | `6969` |
| `sync_service` | `8787` | `8787` |
| FusionAuth | `9011` | `9011` |
| Postgres | `5432` | `5432` |
| Redis | `6379` | `6379` |
| Redis Stack UI | `8001` | `8001` |
| OpenSearch REST | `9200` | `9200` |
| OpenSearch performance analyzer | `9600` | `9600` |

## Shared variables

### Environment and infrastructure

| Variable | Required by | Default | Notes |
| --- | --- | --- | --- |
| `ENVIRONMENT` | Most services | `prod` fallback | Values: `prod`, `dev`, `local`. |
| `PORT` | HTTP services | Usually `8080` | `lexical_service` defaults to `8096`; `websocket_service` uses `6969`. |
| `DATABASE_URL` | Most database-backed services | None | Primary MacroDB connection URL or secret ID where explicitly resolved. |
| `DATABASE_URL_READONLY` | `document_storage_service`, `search_processing_service` | Optional | DSS falls back to primary if readonly connection fails; search backfills use primary when absent or unreachable. |
| `MACRO_DB_URL` | `email_service`, `connection_gateway` | None | Primary MacroDB URL for services that use this name instead of `DATABASE_URL`. |
| `REDIS_URI` | Auth, DSS, email, notification, contacts, workers | None | Redis URL. |
| `REDIS_HOST` | `connection_gateway`, DCS | None | Redis URL-like host used by typed config. |
| `REDIS_URL` | MCP service and some stream/test paths | None | Separate name used by MCP auth proxy wiring. |
| `LOCAL_AWS_URL` | AWS client factory | Optional | Enables LocalStack endpoint and test credentials. |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_DEFAULT_REGION` | LocalStack / AWS SDK | Local E2E sets test credentials | Needed by local AWS CLI setup and SDK calls when not using ambient credentials. |
| `DD_SERVICE`, `DD_ENV` | Deployed tracing | `unknown-service`, `unknown` | Used by OpenTelemetry/Datadog entrypoint in `dev` and `prod`. |

### Shared auth and JWT

| Variable | Required by | Default | Notes |
| --- | --- | --- | --- |
| `INTERNAL_API_SECRET_KEY` | Internal-auth protected services | None | Validated against `x-internal-auth-key`. Local value is literal; deployed uses secret-manager resolution where wired through `LocalOrRemoteSecret`. |
| `AUDIENCE` | Services constructing JWT validation | None | JWT validation config. |
| `ISSUER` | Services constructing JWT validation | None | JWT validation config. |
| `JWT_SECRET_KEY` | Services constructing JWT validation | None | Local value or deployed secret ID. |
| `MACRO_API_TOKEN_ISSUER` | Auth and JWT validation | None | Used for Macro API tokens. |
| `MACRO_API_TOKEN_PUBLIC_KEY` | JWT validation | None | Local public key or deployed secret ID. |
| `MACRO_API_TOKEN_PRIVATE_SECRET_KEY` | `authentication_service` | None | Local private key or deployed secret ID for Macro API token signing. |
| `MACRO_API_TOKEN_EXPIRY_SECONDS` | `authentication_service` | None | Parsed as `usize`. |
| `STRIPE_WEBHOOK_SECRET_KEY` | `authentication_service` | None | Local value or deployed secret ID. |

## Service reference

### `authentication_service`

Required:

| Variable group | Variables |
| --- | --- |
| Base URLs and stores | `BASE_URL`, `DATABASE_URL`, `REDIS_URI`, `DOCUMENT_STORAGE_SERVICE_URL` |
| FusionAuth | `FUSIONAUTH_TENANT_ID`, `FUSIONAUTH_API_KEY_SECRET_KEY`, `FUSIONAUTH_CLIENT_ID`, `FUSIONAUTH_CLIENT_SECRET_KEY`, `FUSIONAUTH_BASE_URL`, `FUSIONAUTH_OAUTH_REDIRECT_URI` |
| Google OAuth | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET_KEY` |
| Stripe | `STRIPE_SECRET_KEY`, `STRIPE_PRICE_ID_HAIKU`, `STRIPE_PRICE_ID_SONNET`, `STRIPE_PRICE_ID_OPUS` |
| GitHub OAuth | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_IDP_ID` |
| Queues | `NOTIFICATION_QUEUE`, `SEARCH_EVENT_QUEUE`, `LINK_MANAGER_QUEUE`, `EMAIL_BACKFILL_QUEUE` |
| Internal auth | `SERVICE_INTERNAL_AUTH_KEY`, plus shared `INTERNAL_API_SECRET_KEY` in service startup |

Optional:

| Variable | Default | Notes |
| --- | --- | --- |
| `PORT` | `8080` | HTTP listener. |
| `GA_MEASUREMENT_ID`, `GA_API_SECRET` | None | Google Analytics Measurement Protocol. |
| `META_PIXEL_ID`, `META_ACCESS_TOKEN`, `META_TEST_EVENT_CODE` | None | Meta conversions tracking. |
| `POSTHOG_API_KEY`, `POSTHOG_HOST` | None | PostHog analytics. |

The active Stripe price ID is selected in code by `ENVIRONMENT`: production uses the production price ID, while `dev` and `local` use the development price ID.

### `document_storage_service`

Required:

| Variable group | Variables |
| --- | --- |
| Stores | `DATABASE_URL`, `DATABASE_URL_READONLY`, `REDIS_URI`, `DOCUMENT_STORAGE_BUCKET`, `DOCX_DOCUMENT_UPLOAD_BUCKET`, `UPLOAD_STAGING_BUCKET`, `BULK_UPLOAD_REQUESTS_TABLE` |
| Queues | `DOCUMENT_DELETE_QUEUE`, `NOTIFICATION_QUEUE`, `SEARCH_EVENT_QUEUE`, `CONTACTS_QUEUE` |
| Service URLs | `CONNECTION_GATEWAY_URL`, `SYNC_SERVICE_URL`, `LEXICAL_SERVICE_URL`, `GITHUB_SYNC_APP_URL` |
| Sync and search | `SYNC_SERVICE_AUTH_KEY`, `OPENSEARCH_URL`, `OPENSEARCH_USERNAME`, `OPENSEARCH_PASSWORD` |
| CloudFront | `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_DISTRIBUTION_URL`, `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID`, `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY_SECRET_NAME` |
| Secrets and integrations | `DOCUMENT_PERMISSION_JWT_SECRET_KEY`, `GITHUB_WEBHOOK_SECRET_KEY`, `GITHUB_SYNC_APP_PEM_SECRET_KEY`, `GITHUB_SYNC_APP_CLIENT_ID`, `CAL_WEBHOOK_SECRET_KEY`, `CAL_EVENT_TYPE_CONTENT_NAMES_KEY`, `META_PIXEL_ID`, `META_ACCESS_TOKEN` |
| LiveKit calls | `LIVEKIT_SERVER_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` |

Optional and tunable:

| Variable | Default | Notes |
| --- | --- | --- |
| `PORT` | `8080` | HTTP listener. |
| `DOCUMENT_LIMIT` | `20` | Free-user document limit. |
| `DOCUMENT_STORAGE_SERVICE_PRESIGNED_URL_EXPIRY_SECONDS` | `900` | Signed document URL TTL. |
| `DOCUMENT_STORAGE_SERVICE_PRESIGNED_URL_BROWSER_CACHE_EXPIRY_SECONDS` | `840` | Browser cache suggestion for signed URLs. |
| `QUEUE_MAX_MESSAGES` | `10` | Delete-document worker poll batch size. |
| `QUEUE_WAIT_TIME_SECONDS` | `4` | Delete-document worker long-poll wait. |
| `META_TEST_EVENT_CODE` | None | Meta test events. |
| `LIVEKIT_TRANSCRIPTION_AGENT_NAME` | None | Requires `INTERNAL_CALL_SECRET` when set. |
| `INTERNAL_CALL_SECRET` | None | Shared secret for internal call endpoints. |
| `CALL_RECORDING_S3_BUCKET`, `CALL_RECORDING_S3_REGION`, `CALL_RECORDING_S3_ACCESS_KEY`, `CALL_RECORDING_S3_SECRET` | None | Call recording egress is enabled only when all four are present. |
| `APPLE_BUNDLE_ID`, `SNS_APNS_VOIP_PLATFORM_ARN` | None | Optional VoIP push sender. Empty VoIP ARN disables VoIP push. |

### `document_cognition_service`

Required:

| Variable group | Variables |
| --- | --- |
| Stores | `DATABASE_URL`, `DOCUMENT_STORAGE_BUCKET`, `DOCX_DOCUMENT_UPLOAD_BUCKET`, `REDIS_HOST` |
| Service URLs | `DOCUMENT_STORAGE_SERVICE_URL`, `DOCUMENT_COGNITION_SERVICE_URL`, `SYNC_SERVICE_URL`, `LEXICAL_SERVICE_URL`, `EMAIL_SERVICE_URL`, `STATIC_FILE_SERVICE_URL`, `AUTHENTICATION_SERVICE_URL` |
| Queues | `DOCUMENT_TEXT_EXTRACTOR_QUEUE`, `CHAT_DELETE_QUEUE`, `EMAIL_SCHEDULED_QUEUE`, `NOTIFICATION_QUEUE`, `SEARCH_EVENT_QUEUE` |
| Secrets | `SYNC_SERVICE_AUTH_KEY`, `AUTHENTICATION_SERVICE_SECRET_KEY`, `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY_SECRET_NAME`, `MCP_CREDENTIALS_KEY_SECRET_NAME` |
| CloudFront | `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_DISTRIBUTION_URL`, `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID` |

Optional:

| Variable | Default | Notes |
| --- | --- | --- |
| `PORT` | `8080` | HTTP listener. |
| `DOCUMENT_BATCH_LIMIT` | `1000` | Maximum document query batch size. |
| `OPENAI_API_KEY` | Empty string | Used by the non-streaming OpenAI chat completions proxy. Missing key still sends an upstream request with an empty bearer token. |

### `email_service`

Required:

| Variable group | Variables |
| --- | --- |
| Stores | `MACRO_DB_URL`, `REDIS_URI`, `ATTACHMENT_BUCKET` |
| Queues | `LINK_MANAGER_QUEUE`, `EMAIL_SCHEDULED_QUEUE`, `GMAIL_INBOX_SYNC_QUEUE`, `GMAIL_INBOX_SYNC_RETRY_QUEUE`, `GMAIL_OPS_QUEUE`, `GMAIL_OPS_RETRY_QUEUE`, `SEARCH_EVENT_QUEUE`, `GMAIL_GCP_QUEUE`, `NOTIFICATION_QUEUE`, `BACKFILL_QUEUE`, `CONTACTS_QUEUE`, `SFS_UPLOADER_QUEUE`, `SFS_DELETE_QUEUE` |
| Service URLs | `AUTHENTICATION_SERVICE_URL`, `STATIC_FILE_SERVICE_URL`, `DOCUMENT_STORAGE_SERVICE_URL`, `CONNECTION_GATEWAY_URL` |
| Secrets | `AUTHENTICATION_SERVICE_SECRET_KEY`, `EMAIL_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY` |
| CloudFront | `EMAIL_SERVICE_CLOUDFRONT_DISTRIBUTION_URL`, `EMAIL_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID` |
| Feature gates | `NOTIFICATIONS_ENABLED` |

Optional and tunable:

| Variable | Default | Notes |
| --- | --- | --- |
| `PORT` | `8080` | HTTP listener. |
| `SENT_UNDO_DELAY_SECS` | `10` | Delay before processing sent mail. |
| `USE_APOLLO_CRM_ENRICHMENT` | `false` | Enables Apollo.io CRM enrichment. |
| `APOLLO_API_KEY` | Empty string | Literal key locally; deployed comments indicate secret name/value resolution by startup wiring. |
| `QUEUE_MAX_MESSAGES` | `10` | Generic queue poll batch size. |
| `QUEUE_WAIT_TIME_SECONDS` | `20` | Generic long-poll wait. |
| `BACKFILL_QUEUE_WORKERS` | `25` | Backfill worker count. |
| `BACKFILL_QUEUE_MAX_MESSAGES` | `1` | Backfill batch size. |
| `INBOX_SYNC_QUEUE_WORKERS` | `10` | Gmail inbox sync worker count. |
| `INBOX_SYNC_QUEUE_MAX_MESSAGES` | `1` | Gmail inbox sync batch size. |
| `INBOX_SYNC_RETRY_QUEUE_WORKERS` | `10` | Gmail retry worker count. |
| `INBOX_SYNC_RETRY_QUEUE_MAX_MESSAGES` | `1` | Gmail retry batch size. |
| `GMAIL_OPS_QUEUE_WORKERS` | `5` | Gmail ops worker count. |
| `GMAIL_OPS_QUEUE_MAX_MESSAGES` | `10` | Gmail ops batch size. |
| `GMAIL_OPS_RETRY_QUEUE_WORKERS` | `2` | Gmail ops retry worker count. |
| `GMAIL_OPS_RETRY_QUEUE_MAX_MESSAGES` | `10` | Gmail ops retry batch size. |
| `SFS_UPLOADER_WORKERS` | `3` | Static-file upload mapper worker count. |
| `REDIS_RATE_LIMIT_REQS` | `14000` | Sliding-window request limit. |
| `REDIS_RATE_LIMIT_REQS_BACKFILL` | `13000` | Backfill-specific rate limit. |
| `REDIS_RATE_LIMIT_WINDOW_SECS` | `60` | Rate-limit window. |
| `EMAIL_SERVICE_PRESIGNED_URL_TTL_SECS` | `3600` | Attachment signed URL TTL. |

### `notification_service`

Required:

| Variable group | Variables |
| --- | --- |
| Base and stores | `BASE_URL`, `DATABASE_URL`, `REDIS_URI` |
| Internal auth | `INTERNAL_API_SECRET_KEY`, `URL_SIGNING_HMAC` |
| Queues | `NOTIFICATION_QUEUE`, `NOTIFICATION_INGRESS_QUEUE`, `PUSH_NOTIFICATION_EVENT_HANDLER_QUEUE` |
| Push providers | `SNS_APNS_PLATFORM_ARN`, `SNS_FCM_PLATFORM_ARN`, `APPLE_BUNDLE_ID` |
| Service URLs | `CONNECTION_GATEWAY_URL` |
| Email sender | `SENDER_BASE_ADDRESS` |

Optional and tunable:

| Variable | Default | Notes |
| --- | --- | --- |
| `PORT` | `8080` | HTTP listener. |
| `NOTIFICATION_QUEUE_MAX_MESSAGES` | `9` | Notification worker poll batch size. |
| `NOTIFICATION_QUEUE_WAIT_TIME_SECONDS` | `4` | Notification worker long-poll wait. |
| `SNS_APNS_VOIP_PLATFORM_ARN` | Required except local | Local can omit it; service uses an empty string locally to keep VoIP disabled. |

`SENDER_BASE_ADDRESS` is transformed into `no-reply@...`, `no-reply-dev@...`, or `no-reply-local@...` according to `ENVIRONMENT`.

### Search services

#### `search_service`

| Variable | Required | Default | Notes |
| --- | --- | --- | --- |
| `DATABASE_URL` | Yes | None | MacroDB connection. |
| `OPENSEARCH_URL` | Yes | None | OpenSearch endpoint. |
| `OPENSEARCH_USERNAME` | Yes | None | OpenSearch username. |
| `OPENSEARCH_PASSWORD` | Yes | None | Local password or deployed secret where caller resolves it. |
| `INTERNAL_API_SECRET_KEY` | Yes | None | Internal API auth. |
| `PORT` | No | `8080` | HTTP listener. |

#### `search_processing_service`

| Variable | Required | Default | Notes |
| --- | --- | --- | --- |
| `DATABASE_URL` | Yes | None | Local URL or deployed secret ID. |
| `DATABASE_URL_READONLY` | No | None | Optional read-replica URL or deployed secret ID for backfills. |
| `SEARCH_EVENT_QUEUE` | Yes | None | Queue consumed for indexing work. |
| `OPENSEARCH_URL` | Yes | None | OpenSearch endpoint. |
| `OPENSEARCH_USERNAME` | Yes | None | OpenSearch username. |
| `OPENSEARCH_PASSWORD` | Yes | None | Local password or deployed secret ID. |
| `DOCUMENT_STORAGE_BUCKET` | Yes | None | Source document bucket. |
| `LEXICAL_SERVICE_URL` | Yes | None | Lexical conversion service. |
| `BACKFILL_JOBS_TABLE` | Yes | None | DynamoDB job registry table. |
| `BACKFILL_JOB_TTL_SECONDS` | No | `86400` | TTL for completed job records. |
| `WORKER_COUNT` | No | `10` | Search worker count. |
| `QUEUE_MAX_MESSAGES` | No | `10` | Queue poll batch size. |
| `QUEUE_WAIT_TIME_SECONDS` | No | `20` | Queue long-poll wait. |
| `BACKFILL_CALLS_PAGE_SIZE` | No | `2000` | Must be `> 0`. |
| `BACKFILL_CHATS_PAGE_SIZE` | No | `5000` | Must be `> 0`. |
| `BACKFILL_CHANNELS_PAGE_SIZE` | No | `5000` | Must be `> 0`. |
| `BACKFILL_DOCUMENTS_PAGE_SIZE` | No | `1000` | Must be `> 0`. |
| `BACKFILL_EMAILS_PAGE_SIZE` | No | `1000` | Must be `> 0`. |
| `PORT` | No | `8080` | HTTP listener. |

### Other HTTP services and workers

| Service | Required variables | Optional/defaults |
| --- | --- | --- |
| `connection_gateway` | `REDIS_HOST`, `MACRO_DB_URL`, `CONNECTION_GATEWAY_TABLE`, shared JWT vars, `INTERNAL_API_SECRET_KEY` | `PORT=8080` |
| `contacts_service` | `DATABASE_URL`, `REDIS_URI`, `CONTACTS_QUEUE`, shared JWT vars | `PORT=8080`, `CONTACTS_QUEUE_MAX_MESSAGES=10`, `CONTACTS_QUEUE_WAIT_TIME_SECONDS=5`, optional `CONNECTION_GATEWAY_URL` |
| `static_file_service` | `STATIC_FILE_SERVICE_DYNAMODB_TABLE_NAME`, `STATIC_STORAGE_BUCKET`, `STATIC_FILE_SERVICE_URL`, `STATIC_FILE_SERVICE_S3_EVENT_QUEUE_URL`, `INTERNAL_API_SECRET_KEY`, shared JWT vars | `PORT=8080` |
| `convert_service` | `CONVERT_QUEUE`, `LOK_PATH`, `DATABASE_URL`, `DOCUMENT_STORAGE_BUCKET`, `WEB_SOCKET_RESPONSE_LAMBDA` | `PORT=8080`, `QUEUE_MAX_MESSAGES=5`, `QUEUE_WAIT_TIME_SECONDS=5` |
| `deleted_item_poller` | `DATABASE_URL`, `DOCUMENT_DELETE_QUEUE`, `CHAT_DELETE_QUEUE`, `SEARCH_EVENT_QUEUE` | None |
| `docx_unzip_handler` | `DATABASE_URL`, `REDIS_URI`, `DOCUMENT_STORAGE_BUCKET`, `WEB_SOCKET_RESPONSE_LAMBDA`, `CONVERT_QUEUE` | None |
| `document_text_extractor` | `DATABASE_URL` | `PDFIUM_LIB_PATH` is embedded at build time. |
| `document_upload_finalizer` local worker | `DATABASE_URL`, `INTERNAL_API_SECRET_KEY`, `SYNC_SERVICE_AUTH_KEY`, `LEXICAL_SERVICE_URL`, `SYNC_SERVICE_URL`, `DOCUMENT_UPLOAD_FINALIZER_QUEUE_URL` | `LOCAL_AWS_URL=http://localstack:4566` in Compose. |
| `email_refresh_handler` | `DATABASE_URL`, `LINK_MANAGER_QUEUE`, `DELETE_UNUSED_AFTER_DAYS`, `DELETE_INACTIVE_AFTER_DAYS` | None |
| `email_scheduled_handler` | `DATABASE_URL`, `EMAIL_SCHEDULED_QUEUE` | None |
| `email_sfs_delete_handler` | `DATABASE_URL`, `SFS_DELETE_QUEUE` | None |
| `sha_cleanup_worker` | `REDIS_URI`, `DATABASE_URL`, `DOCUMENT_STORAGE_BUCKET` | None |
| `unfurl_service` | None beyond shared runtime | `PORT=8080` |
| `image_proxy_service` | None beyond shared runtime | `PORT=8080` |

## Provider-specific variables

| Provider / integration | Variables | Required by |
| --- | --- | --- |
| FusionAuth | `FUSIONAUTH_TENANT_ID`, `FUSIONAUTH_API_KEY_SECRET_KEY`, `FUSIONAUTH_CLIENT_ID`, `FUSIONAUTH_CLIENT_SECRET_KEY`, `FUSIONAUTH_BASE_URL`, `FUSIONAUTH_OAUTH_REDIRECT_URI` | Auth service; MCP service uses the same base/client/tenant secret-key pattern. |
| Google OAuth | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET_KEY` | Auth service and MCP auth proxy. |
| Stripe | `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET_KEY`, legacy `STRIPE_PRICE_ID_*` vars | Auth service. |
| GitHub | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_IDP_ID`, `GITHUB_WEBHOOK_SECRET_KEY`, `GITHUB_SYNC_APP_PEM_SECRET_KEY`, `GITHUB_SYNC_APP_CLIENT_ID`, `GITHUB_SYNC_APP_URL` | Auth and document storage. |
| OpenSearch | `OPENSEARCH_URL`, `OPENSEARCH_USERNAME`, `OPENSEARCH_PASSWORD` | Search and document services. |
| OpenAI | `OPENAI_API_KEY` | DCS completions proxy. |
| Anthropic | `ANTHROPIC_API_KEY` | Anthropic client / AI tools. |
| Slack MCP | `SLACK_MCP_CLIENT_ID`, `SLACK_MCP_CLIENT_SECRET` | Pre-registered MCP provider registry; optional because registry creation tolerates absence. |
| Apollo.io | `USE_APOLLO_CRM_ENRICHMENT`, `APOLLO_API_KEY` | Email CRM enrichment. |
| Meta | `META_PIXEL_ID`, `META_ACCESS_TOKEN`, `META_TEST_EVENT_CODE` | Auth analytics and DSS Cal-to-Meta tracking. |
| Google Analytics | `GA_MEASUREMENT_ID`, `GA_API_SECRET` | Auth analytics. |
| PostHog | `POSTHOG_API_KEY`, `POSTHOG_HOST`, frontend `VITE_POSTHOG_API_KEY` | Auth analytics and web app builds. |
| LiveKit | `LIVEKIT_SERVER_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET`, `LIVEKIT_TRANSCRIPTION_AGENT_NAME` | DSS call service. |
| AWS SNS push | `SNS_APNS_PLATFORM_ARN`, `SNS_FCM_PLATFORM_ARN`, `SNS_APNS_VOIP_PLATFORM_ARN`, `APPLE_BUNDLE_ID` | Notification service and optional DSS VoIP push. |
| Cal.com | `CAL_WEBHOOK_SECRET_KEY`, `CAL_EVENT_TYPE_CONTENT_NAMES_KEY` | DSS Cal webhook routing. |

## Local queues, tables, and buckets

LocalStack setup creates these queue names:

| Variable | Local name |
| --- | --- |
| `NOTIFICATION_QUEUE` | `notification-queue` |
| `NOTIFICATION_INGRESS_QUEUE` | `notification-ingress-queue` |
| `PUSH_NOTIFICATION_EVENT_HANDLER_QUEUE` | `push-delivery-queue` |
| `BACKFILL_QUEUE`, `EMAIL_BACKFILL_QUEUE` | `email-service-backfill-queue` |
| `CHAT_DELETE_QUEUE` | `delete-chat-handler-queue` |
| `CONTACTS_QUEUE` | `contacts-queue` |
| `CONVERT_QUEUE` | `convert-service-queue` |
| `DOCUMENT_DELETE_QUEUE` | `delete-document-handler-queue` |
| `DOCUMENT_UPLOAD_FINALIZER_QUEUE_URL` | `document-upload-finalizer-queue` URL |
| `DOCUMENT_TEXT_EXTRACTOR_QUEUE` | `document-text-extractor-lambda-queue` |
| `EMAIL_SCHEDULED_QUEUE` | `email-service-scheduled-queue` |
| `GMAIL_INBOX_SYNC_QUEUE` | `email-service-gmail-inbox-sync-queue` |
| `GMAIL_INBOX_SYNC_RETRY_QUEUE` | `email-service-gmail-inbox-retry-queue` |
| `GMAIL_OPS_QUEUE` | `email-service-gmail-ops-queue` |
| `GMAIL_OPS_RETRY_QUEUE` | `email-service-gmail-ops-retry-queue` |
| `LINK_MANAGER_QUEUE` | `email-service-refresh-queue` |
| `SEARCH_EVENT_QUEUE` | `search-event-queue` |
| `SFS_DELETE_QUEUE` | `email-sfs-delete-queue` |
| `SFS_UPLOADER_QUEUE` | `email-service-sfs-mapper-queue` |
| `STATIC_FILE_SERVICE_S3_EVENT_QUEUE_URL` | `static-file-s3-event-notification-queue` URL |

LocalStack setup creates these tables and buckets:

| Type | Names |
| --- | --- |
| DynamoDB tables | `bulk-upload`, `connection-gateway-table`, `static-file-metadata` |
| S3 buckets | `macro-email-attachments`, `doc-storage`, `docx-upload`, `static-file-storage`, `bulk-upload-staging` |

## Frontend and JS runtime variables

| Surface | Variables | Defaults / notes |
| --- | --- | --- |
| Vite app dev server | `PORT`, `MODE`, `LOCAL_DOCKER`, `LOCAL_JWT`, `TAURI_DEV_HOST` | App Vite config defaults `PORT` to `3000`; `LOCAL_JWT` is injected as `import.meta.env.__LOCAL_JWT__`. |
| Local backend selection | `VITE_LOCAL_SERVERS`, `VITE_ENABLE_BEARER_TOKEN_AUTH` | Playwright local E2E sets `VITE_LOCAL_SERVERS=ALL` and bearer-token auth. |
| Observability | `VITE_DD_WEB_APP_ID`, `VITE_DD_WEB_APP_TOKEN`, `VITE_POSTHOG_API_KEY` | Used by web observability and analytics packages. |
| Feature flags | `VITE_<FLAG_NAME>` | Feature flag helper reads Vite env keys by flag name. |
| Playwright local E2E | `LOCAL_E2E`, `LOCAL_JWT`, `PORT`, `CI` | `LOCAL_E2E=true` switches tests to the local stack. |
| `lexical_service` | `PORT`, `INTERNAL_AUTH_KEY` or `INTERNAL_API_SECRET_KEY`, `SYNC_SERVICE_AUTH_KEY`, `SYNC_SERVICE_URL` | Compose sets `PORT=8096`, `INTERNAL_AUTH_KEY=${INTERNAL_API_SECRET_KEY}`, and `SYNC_SERVICE_URL=http://sync-service:8787`. |

## Troubleshooting startup failures

| Symptom | Check |
| --- | --- |
| Service exits with `... must be provided` | The named required variable is missing from `.env`, Compose overrides, or deployment config. |
| Service panics while parsing a number | Verify numeric variables such as `PORT`, queue worker counts, TTLs, and page sizes contain only valid positive integers where required. |
| Local service attempts deployed AWS | Set `LOCAL_AWS_URL=http://localstack:4566` and local AWS test credentials. |
| Deployed service treats a secret value as a name | For variables resolved through Secrets Manager in `dev`/`prod`, set the env var to the secret ID, not the plaintext secret. |
| JWT-protected routes fail | Verify `AUDIENCE`, `ISSUER`, `JWT_SECRET_KEY`, `MACRO_API_TOKEN_ISSUER`, and `MACRO_API_TOKEN_PUBLIC_KEY` are aligned across auth-producing and auth-consuming services. |
| Internal service calls return `401` | Verify caller and callee share `INTERNAL_API_SECRET_KEY`; requests must send `x-internal-auth-key`. |
| Local E2E refuses to seed | The seed path requires `LOCAL_E2E_SEED=true` and a local Docker database URL. |
| VoIP push is unexpectedly disabled | DSS requires both `APPLE_BUNDLE_ID` and non-empty `SNS_APNS_VOIP_PLATFORM_ARN`; notification service allows missing VoIP ARN only in local mode. |

## Related pages

- Running locally
- Service architecture
- Local E2E smoke tests
