Skip to main content

Environment Variables

Complete reference for .env.production. Copy .env.production.example and fill in every value. Never commit .env.production to git.

:::info One deployment, all tenants These variables configure the platform (one shared deployment), not a single customer. Per-customer settings — mailboxes, calendar, roles — are stored per tenant in the database and configured in-app, not here. :::

Database

VariableDescription
DATABASE_URLPostgreSQL connection string for the shared database that holds all tenants, e.g. postgresql://user:pass@host:5432/db?sslmode=require

Microsoft Entra ID (platform SSO)

From the platform Azure App Registration:

VariableDescription
ENTRA_TENANT_IDDirectory (tenant) ID of the platform SSO app
ENTRA_CLIENT_IDApplication (client) ID
ENTRA_CLIENT_SECRETClient secret value

:::note Email-to-ticket uses this same registration Earlier versions let each tenant bring its own Azure app registration for mail. That was removed — email now authenticates with this app against each tenant's registered Entra directory. The app-only connection mode additionally requires Mail.ReadWrite and Mail.Send application permissions on it. :::

Azure Blob Storage (attachments)

VariableDescription
AZURE_STORAGE_ACCOUNT_NAMEStorage account name
AZURE_STORAGE_ACCOUNT_KEYStorage account access key
AZURE_STORAGE_CONTAINER_NAMEBlob container for attachments (e.g. attachments)

Application

VariableDescription
APP_URLPublic HTTPS URL of the instance — must be reachable from the internet (Graph webhooks call it)
TEAMS_APP_IDTeams app ID from the manifest
NEXT_PUBLIC_TEAMS_APP_IDSame ID, exposed to the client bundle

Authentication

VariableDescription
BETTER_AUTH_SECRETSession signing secret — generate with openssl rand -hex 32
BETTER_AUTH_URLPublic base URL Better Auth issues callbacks against — normally the same as APP_URL
ENTRA_SSO_AUTHORITYAzure authority for web SSO. Defaults to common (multi-tenant). Set to a specific directory id to lock sign-in to one directory
ENTRA_TID_ENFORCEtrue makes an Azure directory mismatch block the sign-in. Default (anything else) logs it and allows it — see Directory verification

:::caution Turn on ENTRA_TID_ENFORCE deliberately With it off, a Microsoft account from the wrong Azure directory can still sign in — the mismatch is only logged. Register each tenant's entraDirectoryId first, watch the logs for mismatches, then enable it. :::

Outbound Email (SMTP)

Used for acknowledgements and notifications to custom-login users:

VariableDescription
SMTP_HOST / SMTP_PORT / SMTP_SECURESMTP server (e.g. smtp.office365.com, 587, false)
SMTP_USER / SMTP_PASSSMTP credentials
SMTP_FROMFrom address on outbound mail

:::note INTERNAL_DOMAINS is derived, not set The platform's internal domain is derived automatically from the domain of SMTP_USER (e.g. admin@example.comexample.com) — there is no separate variable. Platform-admin eligibility is likewise restricted to the operator's own email domain (a build-time constant), so choose the SMTP sender accordingly. :::

Email-to-Ticket

VariableDescription
GRAPH_WEBHOOK_SECRETRandom string verifying Graph change notifications — generate with openssl rand -hex 16
CRON_SECRETBearer token the worker uses to call /api/cron/email. Falls back to GRAPH_WEBHOOK_SECRET when unset, so existing deployments keep working

:::danger Without one of these, mail does not flow The email cycle refuses unauthenticated triggers. If neither CRON_SECRET nor GRAPH_WEBHOOK_SECRET is set, the worker skips the job entirely and logs a warning — Graph subscriptions are never renewed and polled inboxes report "Not yet polled" forever.

A 401 in the worker's [email-cycle] logs means the worker's secret disagrees with the web app's. :::

Credential Encryption

VariableDescription
EMAIL_ENCRYPTION_KEYAES-256-GCM key, exactly 64 hex charsopenssl rand -hex 32. Encrypts stored mailbox credentials: delegated OAuth refresh tokens and IMAP/SMTP passwords
danger

Changing EMAIL_ENCRYPTION_KEY after go-live invalidates every tenant's stored mailbox credentials — every delegated connection and IMAP password must be re-entered. Store it in a secrets manager and never rotate it casually.

:::note Also required by the web app The Next.js app additionally reads BETTER_AUTH_SECRET (session signing), the AZURE_STORAGE_* variables (attachments), and APP_URL / TEAMS_APP_ID. Set all of them for a working deployment. :::