Skip to documentation

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

VariablePurpose / default
OPENSEND_VERSIONApp, migrate, and SMTP image tag; Compose defaults to latest
COMPOSE_FILEcompose.yaml, or compose.yaml:compose.caddy.yaml for Caddy
COMPOSE_PROJECT_NAMEIsolate another installation's containers and volumes; default project name is opensend
APP_IMAGEOverrides ghcr.io/panarastudios/opensend-app:${OPENSEND_VERSION}
MIGRATE_IMAGEOverrides ghcr.io/panarastudios/opensend-migrate:${OPENSEND_VERSION}
SMTP_IMAGEOverrides ghcr.io/panarastudios/opensend-smtp:${OPENSEND_VERSION}
CONVEX_IMAGEOverrides the release's digest-pinned Convex backend
CONVEX_DASHBOARD_IMAGEOverrides the digest-pinned optional Convex dashboard
CADDY_IMAGEOverrides the pinned Caddy image in compose.caddy.yaml
DOCUMENT_RETENTION_DELAYConvex 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

VariablePurpose / default
SITE_URLPublic Opensend dashboard origin; required with Caddy
CONVEX_PUBLIC_URLBrowser queries and WebSockets; http://localhost:3210 in base Compose
CONVEX_PUBLIC_SITE_URLPublic Convex HTTP origin: REST API, auth, SES callbacks, tracking and unsubscribe; http://localhost:3211 in base Compose
CONVEX_BACKEND_ORIGINAdvertised Convex backend/storage origin, reachable inside Docker; http://host.docker.internal:3210
CONVEX_INTERNAL_URLApp server queries; fixed to http://convex:3210 by Compose; http://localhost:3210 in development
CONVEX_INTERNAL_SITE_URLApp 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.

dotenv
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.com

Your 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

VariablePurpose
INSTANCE_NAMEBackend instance name; installer default opensend
INSTANCE_SECRETBackend secret used with the instance name to mint the admin key
CONVEX_SELF_HOSTED_ADMIN_KEYDeployment admin credential used by migrate and the Convex dashboard
BETTER_AUTH_SECRETBackend authentication secret; generated by install/setup
SSO_ENCRYPTION_KEYEncrypts SSO settings; generated by install/setup
SES_ENCRYPTION_KEYNeeded only to restore legacy v1 credentials; current encryption is created by the AWS wizard
SES_CALLBACK_ORIGINOptional public HTTPS SES callback origin override
SMTP_HOSTPublic SMTP hostname; sent to Convex and the optional gateway
DOMAIN_CONNECT_KEYKey ID override for Domain Connect signing
DOMAIN_CONNECT_PRIVATE_KEYPrivate signing key override for Domain Connect
DOMAIN_CONNECT_SIGNERDomain Connect signer URL override; the default signer is opensend.cc
ALLOW_LOCAL_OIDCAllow the local OIDC test origin; keep disabled in production
LOG_AUTH_LINKSDevelopment-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

VariablePurpose / default
APP_PORTOpensend dashboard app, 3000; Caddy binds it to loopback
CONVEX_PORTBackend queries, loopback 3210
CONVEX_SITE_PORTBackend HTTP actions, loopback 3211
DASHBOARD_PORTOptional Convex dashboard, loopback 6791
SMTP_TLS_PUBLIC_PORTOptional SMTP implicit TLS listener, 465 to container 2465
SMTP_STARTTLS_PUBLIC_PORTOptional SMTP STARTTLS listener, 587 to container 2587
SMTP_CERT_DIRHost certificate directory; ./docker/smtp/certs, mounted read-only at /certs
SMTP_MAX_MESSAGE_BYTESSMTP 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:

FlagEnvironment variable / default
--dir PATHOPENSEND_DIR, ./opensend
--version TAGOPENSEND_VERSION, latest GitHub release
--domain HOSTOPENSEND_DOMAIN
--api-domain HOSTOPENSEND_API_DOMAIN, api.<domain>: REST API, callbacks and tracking
--realtime-domain HOSTOPENSEND_REALTIME_DOMAIN, realtime.<domain>: the dashboard’s live updates
--caddy yes|noOPENSEND_CADDY, yes
--yesOPENSEND_YES=1
--localOPENSEND_LOCAL=1; localhost URLs without Caddy
--source-url URLOPENSEND_SOURCE_URL, release asset base URL
--no-startOPENSEND_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.