Self-hosting
Environment variables
Configure Compose images, public origins, ports, and backend settings.
Where settings live
The installer writes ./opensend/.env with mode 0600. Manual installs also use .env, which Compose reads automatically. Source setup uses .env.docker; pass --env-file .env.docker to Compose. .env.example describes .env.local for pnpm dev and direct CLI/editor use.
Keep secrets private and back them up with your data. Do not add NEXT_PUBLIC_ to secret names. Installer reruns preserve existing values and never rotate existing secrets.
Release and images
| Variable | Purpose / default |
|---|---|
OPENSEND_VERSION | App, migrate, and SMTP image tag; Compose defaults to latest |
COMPOSE_FILE | compose.yaml, or compose.yaml:compose.caddy.yaml for Caddy |
COMPOSE_PROJECT_NAME | Isolate another installation's containers and volumes; default project name is opensend |
APP_IMAGE | Overrides ghcr.io/panarastudios/opensend-app:${OPENSEND_VERSION} |
MIGRATE_IMAGE | Overrides ghcr.io/panarastudios/opensend-migrate:${OPENSEND_VERSION} |
SMTP_IMAGE | Overrides ghcr.io/panarastudios/opensend-smtp:${OPENSEND_VERSION} |
CONVEX_IMAGE | Overrides the release's digest-pinned Convex backend |
CONVEX_DASHBOARD_IMAGE | Overrides the digest-pinned optional Convex dashboard |
CADDY_IMAGE | Overrides the pinned Caddy image in compose.caddy.yaml |
DOCUMENT_RETENTION_DELAY | Convex backend document retention delay in seconds; 172800 |
OPENSEND_VERSION is the release tag, such as v0.1.0. Each release's images carry the same tag. Source setup saves local image overrides; updating only OPENSEND_VERSION does not replace those images.
DOCUMENT_RETENTION_DELAY is a backend container setting, not the application's email retention policy. The Compose backend also fixes DISABLE_BEACON=true and REDACT_LOGS_TO_CLIENT=true.
Origins
| Variable | Purpose / default |
|---|---|
SITE_URL | Public Opensend dashboard origin; required with Caddy |
CONVEX_PUBLIC_URL | Browser queries and WebSockets; http://localhost:3210 in base Compose |
CONVEX_PUBLIC_SITE_URL | Public Convex HTTP origin: REST API, auth, SES callbacks, tracking and unsubscribe; http://localhost:3211 in base Compose |
CONVEX_BACKEND_ORIGIN | Advertised Convex backend/storage origin, reachable inside Docker; http://host.docker.internal:3210 |
CONVEX_INTERNAL_URL | App server queries; fixed to http://convex:3210 by Compose; http://localhost:3210 in development |
CONVEX_INTERNAL_SITE_URL | App server HTTP actions; fixed to http://convex:3211 by Compose; http://localhost:3211 in development |
The backend maps CONVEX_BACKEND_ORIGIN to CONVEX_CLOUD_ORIGIN and CONVEX_PUBLIC_SITE_URL to CONVEX_SITE_ORIGIN. These advertised origins must be reachable from inside the backend container. Caddy requires all three public URLs as HTTPS origins. The optional dashboard gets NEXT_PUBLIC_DEPLOYMENT_URL from CONVEX_PUBLIC_URL.
SITE_URL=https://mail.example.com
CONVEX_PUBLIC_URL=https://realtime.mail.example.com
CONVEX_PUBLIC_SITE_URL=https://api.mail.example.com
CONVEX_BACKEND_ORIGIN=https://realtime.mail.example.comYour client application's REST base URL is CONVEX_PUBLIC_SITE_URL, without an /api suffix. It is different from the Convex query/WebSocket origin.
Credentials and backend environment
| Variable | Purpose |
|---|---|
INSTANCE_NAME | Backend instance name; installer default opensend |
INSTANCE_SECRET | Backend secret used with the instance name to mint the admin key |
CONVEX_SELF_HOSTED_ADMIN_KEY | Deployment admin credential used by migrate and the Convex dashboard |
BETTER_AUTH_SECRET | Backend authentication secret; generated by install/setup |
SSO_ENCRYPTION_KEY | Encrypts SSO settings; generated by install/setup |
SES_ENCRYPTION_KEY | Needed only to restore legacy v1 credentials; current encryption is created by the AWS wizard |
SES_CALLBACK_ORIGIN | Optional public HTTPS SES callback origin override |
SMTP_HOST | Public SMTP hostname; sent to Convex and the optional gateway |
DOMAIN_CONNECT_KEY | Key ID override for Domain Connect signing |
DOMAIN_CONNECT_PRIVATE_KEY | Private signing key override for Domain Connect |
DOMAIN_CONNECT_SIGNER | Domain Connect signer URL override; the default signer is opensend.cc |
ALLOW_LOCAL_OIDC | Allow the local OIDC test origin; keep disabled in production |
LOG_AUTH_LINKS | Development-only account-link logging when no system sender exists |
The no-argument migrate job sends non-empty values for SITE_URL, BETTER_AUTH_SECRET, SSO_ENCRYPTION_KEY, SES_ENCRYPTION_KEY, SES_CALLBACK_ORIGIN, ALLOW_LOCAL_OIDC, LOG_AUTH_LINKS, all three DOMAIN_CONNECT_* settings, and SMTP_HOST to Convex before deployment. Empty Compose defaults do not erase existing backend settings. For settings changed directly with migrate env set, keep any non-empty .env counterpart in sync.
Without LOG_AUTH_LINKS, bootstrap logs expose account links only for the first installation administrator before a system sender exists. Never enable LOG_AUTH_LINKS=true on a real installation. See account recovery.
Ports and SMTP
| Variable | Purpose / default |
|---|---|
APP_PORT | Opensend dashboard app, 3000; Caddy binds it to loopback |
CONVEX_PORT | Backend queries, loopback 3210 |
CONVEX_SITE_PORT | Backend HTTP actions, loopback 3211 |
DASHBOARD_PORT | Optional Convex dashboard, loopback 6791 |
SMTP_TLS_PUBLIC_PORT | Optional SMTP implicit TLS listener, 465 to container 2465 |
SMTP_STARTTLS_PUBLIC_PORT | Optional SMTP STARTTLS listener, 587 to container 2587 |
SMTP_CERT_DIR | Host certificate directory; ./docker/smtp/certs, mounted read-only at /certs |
SMTP_MAX_MESSAGE_BYTES | SMTP DATA limit; 41943040 (40 MiB) |
Compose fixes SMTP_CONVEX_SITE_URL=http://convex:3211, SMTP_TLS_KEY_PATH=/certs/privkey.pem, and SMTP_TLS_CERT_PATH=/certs/fullchain.pem in the gateway. See SMTP gateway for its opt-in smtp profile and certificates.
CLI and source helpers
migrate receives CONVEX_SELF_HOSTED_URL=http://convex:3210 from Compose. Direct CLI/editor use needs CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY in .env.local. Do not also set CONVEX_DEPLOYMENT. The migrate entrypoint clears CONVEX_DEPLOYMENT and CONVEX_DEPLOY_KEY before passing commands to the CLI.
OPENSEND_ENV_FILE overrides the source helpers' environment file. OPENSEND_BACKEND_ONLY=1 starts only backend services in source setup; OPENSEND_SKIP_BUILD=1 skips its image build. These are helper options, not container settings.
The oidc-test profile is for local testing. OIDC_PORT defaults to loopback 8080, OIDC_ADMIN_PASSWORD defaults to local-test-only, and OIDC_REALM_FILE defaults to ./docker/oidc-realm.json. That realm file is part of the source checkout, not a release asset.
Installer options
These configure the shell installer; they are not backend settings:
| Flag | Environment variable / default |
|---|---|
--dir PATH | OPENSEND_DIR, ./opensend |
--version TAG | OPENSEND_VERSION, latest GitHub release |
--domain HOST | OPENSEND_DOMAIN |
--api-domain HOST | OPENSEND_API_DOMAIN, api.<domain>: REST API, callbacks and tracking |
--realtime-domain HOST | OPENSEND_REALTIME_DOMAIN, realtime.<domain>: the dashboard’s live updates |
--caddy yes|no | OPENSEND_CADDY, yes |
--yes | OPENSEND_YES=1 |
--local | OPENSEND_LOCAL=1; localhost URLs without Caddy |
--source-url URL | OPENSEND_SOURCE_URL, release asset base URL |
--no-start | OPENSEND_NO_START=1 |
The installer saves image overrides, app/backend ports, and COMPOSE_PROJECT_NAME in .env. Existing settings take precedence on install. Upgrade can replace app, migrate, and SMTP image overrides supplied in its environment. uninstall --purge always needs a terminal and typed PURGE; there is no environment variable to bypass it.