Skip to content

Configuration

calit is configured entirely through environment variables. In a Docker Compose deployment these come from your .env file (loaded via env_file). Copy .env.example to .env and edit it before starting the stack.

VariableDescriptionDefault
DB_NAMEPostgres database namecalit
DB_USERPostgres usercalit
DB_PASSWORDPostgres passwordrequired

The compose file derives DB_URL=jdbc:postgresql://db:5432/${DB_NAME} automatically — you do not set DB_URL in .env.

VariableDescriptionDefault
APP_PORTHost port to expose (the container always listens on 8080)8080
APP_BASE_URLPublic origin users hit — e.g. https://book.example.com. Used as the base for invitee manage links and the Google OAuth redirect URIs.required

On first run with an empty database every request redirects to /setup to create the first admin user. There is no default password.

VariableDescriptionDefault
MAIL_HOSTSMTP hostnamerequired
MAIL_USERNAMESMTP usernamerequired
MAIL_PASSWORDSMTP passwordrequired
MAIL_FROMSender address (e.g. calit@example.com)required
MAIL_PORTSMTP portrequired
MAIL_START_TLSSTARTTLS mode: REQUIRED, OPTIONAL, or DISABLEDrequired
MAIL_TLSImplicit TLS (SMTPS): true or falserequired

The port number does not automatically select an encryption mode. You must set MAIL_PORT, MAIL_START_TLS, and MAIL_TLS together. There are two valid combinations:

Option A — port 587, STARTTLS (most common)

Connection starts plaintext and upgrades to TLS via STARTTLS.

MAIL_PORT=587
MAIL_START_TLS=REQUIRED
MAIL_TLS=false

Option B — port 465, implicit TLS / SMTPS

Connection is encrypted from the first byte.

MAIL_PORT=465
MAIL_TLS=true
MAIL_START_TLS=OPTIONAL

MAIL_START_TLS is an enum (REQUIRED / OPTIONAL / DISABLED), not a boolean. MAIL_TLS is a boolean.

Mail is sent synchronously. If a send fails (SMTP down, refused, timing out), the message is parked in a database outbox instead of being lost, and a background tick retries it — every 60 s, on every replica, claimed with SELECT … FOR UPDATE SKIP LOCKED so it is multi-node-safe with no leader election. Retries use exponential backoff (1 min, doubling, capped at 1 h) and stop after 10 attempts; failed rows are kept for inspection.

Booking and password-reset flows therefore never fail just because SMTP is unavailable. Time-sensitive mail carries a deadline: a queued password-reset email is dropped (not delivered) once its 30-minute reset token has expired, so a recovered SMTP server never hands out a dead reset link. No configuration is required — the outbox is always on.

VariableDescriptionDefault
REMINDER_LEAD_MINUTESMinutes before a meeting to send the reminder email1440 (24 h)
APPROVAL_HOLD_HOURSHow long a pending (approval-required) booking is held before it expires24
PER_EMAIL_DAILY_CAPMaximum bookings an invitee email address may make per day (abuse protection)10

calit exposes standard MicroProfile Health endpoints — point your orchestrator or load balancer at these:

EndpointPurpose
GET /q/health/liveLiveness — the process is up. Does not check SMTP or Google, so a flapping external dependency can never get a healthy replica restarted.
GET /q/health/readyReadiness — safe to route traffic. Includes informational SMTP and Google checks.

The SMTP and Google checks are informational: they always report UP and expose reachability under data.state (reachable, unreachable, mocked-or-unconfigured, or not-configured). They never mark a replica DOWN — a down mail server does not pull the replica out of rotation, because outgoing mail falls back to the outbox. Use data.state for observability, not as a gate.

VariableDescriptionDefault
SESSION_ENCRYPTION_KEYSigns and encrypts the login session cookie. At least 16 characters. Must be identical on every replica.required
TOKEN_ENCRYPTION_KEYAES-256-GCM key that encrypts Google OAuth tokens at rest. Must be exactly 64 hex characters. Must be identical on every replica.required in prod

Generate both keys with:

Terminal window
openssl rand -hex 32

Leave GOOGLE_OAUTH_CLIENT_ID blank to run calit in degraded mode without Google Calendar integration. See Google OAuth setup for full instructions.

VariableDescriptionDefault
GOOGLE_OAUTH_CLIENT_IDGoogle OAuth client ID(blank — disables Google)
GOOGLE_OAUTH_CLIENT_SECRETGoogle OAuth client secret(blank)
GOOGLE_OAUTH_REDIRECT_URIOverride the calendar sync redirect URIDerived: ${APP_BASE_URL}/api/google/callback
GOOGLE_OAUTH_LOGIN_REDIRECT_URIOverride the sign-in redirect URIDerived: ${APP_BASE_URL}/api/google/login/callback
GOOGLE_OAUTH_STATE_SECRETStrong random string shared by all replicas. Generate: openssl rand -hex 32. Required when Google is enabled.(blank)

Register both derived redirect URIs in your Google OAuth client even if you do not override them.

Turnstile adds a bot-protection widget to the public booking form. See Turnstile setup for full instructions.

VariableDescriptionDefault
TURNSTILE_ENABLEDEnable Turnstile on the public booking formfalse
TURNSTILE_SITE_KEYTurnstile site key from the Cloudflare dashboard(blank)
TURNSTILE_SECRET_KEYTurnstile secret key(blank)