getting-started
Configuration
All configuration options — config.yaml, environment variables, and runtime overrides.
Configuration Sources
BurnerByte layers configuration, each source overriding the one before it:
- Built-in defaults — registered in
internal/config/config.go config.yaml— optional, searched in.and/etc/burnerbyte/, or atBB_CONFIG_PATH- Environment variables — prefixed with
BB_, where the prefix replaces the dots in the key path (BB_SERVER_PORT→server.port,BB_SMTP_HOSTNAME→smtp.hostname) - Database overrides — applied at startup from the
system_configstable for themailer,storage,ssoandplatformgroups, so setup-wizard and admin-UI values take precedence over the environment for those keys
DATABASE_URL, REDIS_URL, JWT_SECRET and ENCRYPTION_KEY are read without the BB_ prefix.
A BB_* variable only works for a key the binary has registered. Every key in
this reference is registered, and TestConfigExampleCoversEveryRegisteredKey /
TestEnvExampleCoversEveryRegisteredKey keep config.example.yaml and
.env.example exhaustive. If you add a config key, register it with
SetDefault (or BindEnv) or its environment override is silently discarded.
Server
| Key | Env Var | Default | Description |
|---|---|---|---|
server.port | API_PORT | 8080 | HTTP listen port |
server.base_url | API_BASE_URL | http://localhost:8080 | Public API URL |
server.frontend_url | FRONTEND_URL | http://localhost:3000 | Frontend URL (for email links) |
server.read_timeout | BB_SERVER_READ_TIMEOUT | 30s | HTTP read timeout |
server.write_timeout | BB_SERVER_WRITE_TIMEOUT | 30s | HTTP write timeout |
server.idle_timeout | BB_SERVER_IDLE_TIMEOUT | 120s | HTTP idle timeout |
server.shutdown_timeout | BB_SERVER_SHUTDOWN_TIMEOUT | 15s | Graceful shutdown timeout |
server.max_body_size | BB_SERVER_MAX_BODY_SIZE | 1048576 | Max request body size (bytes) |
Database
| Key | Env Var | Default | Description |
|---|---|---|---|
database.url | DATABASE_URL | — | PostgreSQL connection string |
database.max_open_conns | BB_DATABASE_MAX_OPEN_CONNS | 25 | Max open connections |
database.max_idle_conns | BB_DATABASE_MAX_IDLE_CONNS | 5 | Max idle connections |
database.conn_max_lifetime | BB_DATABASE_CONN_MAX_LIFETIME | 5m | Connection max lifetime |
Redis
| Key | Env Var | Default | Description |
|---|---|---|---|
redis.url | REDIS_URL | — | Redis connection string |
redis.max_retries | BB_REDIS_MAX_RETRIES | 3 | Max retry attempts |
JWT
| Key | Env Var | Default | Description |
|---|---|---|---|
jwt.secret | JWT_SECRET | — | Required. Signing secret |
jwt.access_ttl | BB_JWT_ACCESS_TTL | 15m | Access token lifetime |
jwt.refresh_ttl | BB_JWT_REFRESH_TTL | 168h | Refresh token lifetime (7 days) |
SMTP Inbound
| Key | Env Var | Default | Description |
|---|---|---|---|
smtp.listen | BB_SMTP_LISTEN | 0.0.0.0:2525 | SMTP listen address |
smtp.hostname | BB_SMTP_HOSTNAME | localhost | Hostname in the SMTP greeting and the MX/SPF target the API compares domains against during verification — set it on the API process too, or no domain will verify |
smtp.max_size | BB_SMTP_MAX_SIZE | 26214400 | Max message size (25 MB) |
smtp.queue_size | BB_SMTP_QUEUE_SIZE | 1000 | Processing queue buffer |
smtp.workers | BB_SMTP_WORKERS | 4 | Worker goroutines |
smtp.tls_cert | BB_SMTP_TLS_CERT | — | TLS certificate path (enables STARTTLS) |
smtp.tls_key | BB_SMTP_TLS_KEY | — | TLS private key path |
Mailer (Outbound)
| Key | Env Var | Description |
|---|---|---|
mailer.host | BB_MAILER_HOST | SMTP server host |
mailer.port | BB_MAILER_PORT | SMTP server port |
mailer.username | BB_MAILER_USERNAME | Auth username |
mailer.password | BB_MAILER_PASSWORD | Auth password |
mailer.from | BB_MAILER_FROM | Sender address |
mailer.tls | BB_MAILER_TLS | Enable TLS |
Mailer config can be overridden at runtime via the setup wizard. Values are stored in the system_configs database table and loaded on startup.
MinIO / S3
| Key | Env Var | Description |
|---|---|---|
minio.endpoint | BB_MINIO_ENDPOINT | MinIO endpoint |
minio.access_key | BB_MINIO_ACCESS_KEY | Access key |
minio.secret_key | BB_MINIO_SECRET_KEY | Secret key |
minio.bucket | BB_MINIO_BUCKET | Bucket name |
minio.use_ssl | BB_MINIO_USE_SSL | Enable SSL |
Encryption
| Key | Env Var | Default | Description |
|---|---|---|---|
encryption.key | ENCRYPTION_KEY (or BB_ENCRYPTION_KEY) | — | Strongly recommended. 32-byte hex-encoded (64-char) AES-256-GCM key. When empty, SSO client secrets, SMTP passwords and storage credentials are written to the database in plaintext — the API still boots. Docker Compose forwards only the unprefixed name. |
SSO / OIDC
| Key | Env Var | Description |
|---|---|---|
sso.provider | BB_SSO_PROVIDER | OIDC provider name |
sso.client_id | BB_SSO_CLIENT_ID | OAuth client ID |
sso.client_secret | BB_SSO_CLIENT_SECRET | OAuth client secret |
sso.redirect_url | BB_SSO_REDIRECT_URL | Callback URL |
sso.tenant_id | BB_SSO_TENANT_ID | OIDC tenant/realm identifier |
sso.issuer_url | BB_SSO_ISSUER_URL | OIDC issuer URL for discovery |
sso.auto_provision | BB_SSO_AUTO_PROVISION | Auto-create users on first SSO login |
sso.default_org_role | BB_SSO_DEFAULT_ORG_ROLE | Default org role for SSO-provisioned users |
sso.default_team_role | BB_SSO_DEFAULT_TEAM_ROLE | Default team role for SSO-provisioned users |
sso.allowed_domains | BB_SSO_ALLOWED_DOMAINS | Comma-separated allowed email domains |
Rate Limiting
| Key | Env Var | Default | Description |
|---|---|---|---|
rate_limit.enabled | BB_RATE_LIMIT_ENABLED | true | Enable rate limiting |
rate_limit.authenticated | BB_RATE_LIMIT_AUTHENTICATED | 300 | Requests/min (authenticated) |
rate_limit.unauthenticated | BB_RATE_LIMIT_UNAUTHENTICATED | 60 | Requests/min (unauthenticated) |
rate_limit.login | BB_RATE_LIMIT_LOGIN | 10 | Login attempts/min per IP |
rate_limit.forgot_password | BB_RATE_LIMIT_FORGOT_PASSWORD | 3 | Reset requests/hour per IP |
rate_limit.trusted_proxies | BB_RATE_LIMIT_TRUSTED_PROXIES | (empty) | Comma-separated CIDRs (a bare IP is treated as /32) whose X-Forwarded-For is trusted for client-IP resolution |
Account Lockout
| Key | Env Var | Default | Description |
|---|---|---|---|
lockout.max_attempts | BB_LOCKOUT_MAX_ATTEMPTS | 5 | Failed attempts before lockout |
lockout.duration | BB_LOCKOUT_DURATION | 15m | Lockout duration |
Password Policy
| Key | Env Var | Default | Description |
|---|---|---|---|
password_policy.min_length | BB_PASSWORD_POLICY_MIN_LENGTH | 8 | Minimum password length |
password_policy.require_uppercase | BB_PASSWORD_POLICY_REQUIRE_UPPERCASE | true | Require uppercase letter |
password_policy.require_lowercase | BB_PASSWORD_POLICY_REQUIRE_LOWERCASE | true | Require lowercase letter |
password_policy.require_number | BB_PASSWORD_POLICY_REQUIRE_NUMBER | true | Require digit |
password_policy.require_special | BB_PASSWORD_POLICY_REQUIRE_SPECIAL | true | Require special character |
password_policy.bcrypt_cost | BB_PASSWORD_POLICY_BCRYPT_COST | 12 | bcrypt hashing cost (higher is slower and stronger). The timing-equalising dummy comparison that hides user enumeration is derived from this value, so raising it stays safe |
Defaults
| Key | Env Var | Default | Description |
|---|---|---|---|
defaults.attachments_enabled | BB_DEFAULTS_ATTACHMENTS_ENABLED | true | Global attachment toggle |
defaults.allow_registration | BB_DEFAULTS_ALLOW_REGISTRATION | true | Allow new user registration |
defaults.enforce_sso | BB_DEFAULTS_ENFORCE_SSO | false | Enforce SSO for all users |
defaults.default_inbox_ttl | BB_DEFAULTS_DEFAULT_INBOX_TTL | 10m | Default inbox lifetime |
defaults.max_inbox_ttl | BB_DEFAULTS_MAX_INBOX_TTL | 24h | Maximum inbox lifetime |
defaults.max_attachment_size_mb | BB_DEFAULTS_MAX_ATTACHMENT_SIZE_MB | 25 | Max attachment size |
defaults.max_domains | BB_DEFAULTS_MAX_DOMAINS | 10 | Max domains per org |
defaults.max_teams | BB_DEFAULTS_MAX_TEAMS | 50 | Max teams per org |
defaults.max_inboxes_per_domain | BB_DEFAULTS_MAX_INBOXES_PER_DOMAIN | 100 | Max inboxes per domain |
defaults.max_sessions_per_user | BB_DEFAULTS_MAX_SESSIONS_PER_USER | 5 | Max concurrent active sessions per user |
defaults.password_reset_ttl | BB_DEFAULTS_PASSWORD_RESET_TTL | 1h | Password reset token lifetime |
defaults.invite_expiry_ttl | BB_DEFAULTS_INVITE_EXPIRY_TTL | 48h | Invite link lifetime |
defaults.presigned_url_ttl | BB_DEFAULTS_PRESIGNED_URL_TTL | 15m | Attachment download URL lifetime |
defaults.webhook_timeout | BB_DEFAULTS_WEBHOOK_TIMEOUT | 10s | Webhook HTTP timeout |
defaults.webhook_max_retries | BB_DEFAULTS_WEBHOOK_MAX_RETRIES | 3 | Max webhook delivery attempts |
defaults.analytics_cache_ttl | BB_DEFAULTS_ANALYTICS_CACHE_TTL | 2h | Analytics cache duration |
defaults.analytics_default_days | BB_DEFAULTS_ANALYTICS_DEFAULT_DAYS | 30 | Default days for emails-per-day charts |
defaults.timezone | BB_DEFAULTS_TIMEZONE | UTC | Default user timezone |
defaults.date_format | BB_DEFAULTS_DATE_FORMAT | YYYY-MM-DD | Default date display format |
defaults.time_format | BB_DEFAULTS_TIME_FORMAT | 24h | Default time display format (12h or 24h) |
Workers
| Key | Env Var | Default | Description |
|---|---|---|---|
workers.dns_recheck_interval | BB_WORKERS_DNS_RECHECK_INTERVAL | 1h | DNS verification recheck |
workers.cleanup_interval | BB_WORKERS_CLEANUP_INTERVAL | 5m | Expired inbox/session cleanup |
workers.webhook_retry_interval | BB_WORKERS_WEBHOOK_RETRY_INTERVAL | 1m | Failed webhook retry |
workers.reconciler_interval | BB_WORKERS_RECONCILER_INTERVAL | 1m | Redis ↔ Postgres sync |
workers.analytics_interval | BB_WORKERS_ANALYTICS_INTERVAL | 5m | Analytics cache refresh |
Two of the seven workers have hardcoded intervals and no config key:
| Worker | Interval | Description |
|---|---|---|
invite_expiry | 24h | Emails inviters about invites expiring within 24 hours, then deletes lapsed ones |
admin_stats | 10s | Refreshes the cached platform stats streamed over /api/v1/ws/admin-stats |
Every job runs once at startup before its ticker begins, and every API process starts all seven — there is no leader election and no way to run an API instance without them. See Production Deployment.
CORS
| Key | Env Var | Description |
|---|---|---|
cors.allowed_origins | BB_CORS_ALLOWED_ORIGINS | Comma-separated origins |
cors.allowed_methods | BB_CORS_ALLOWED_METHODS | Allowed HTTP methods |
cors.allowed_headers | BB_CORS_ALLOWED_HEADERS | Allowed request headers |
cors.max_age | BB_CORS_MAX_AGE | Preflight cache duration (seconds) |
Email Verification
| Key | Env Var | Default | Description |
|---|---|---|---|
email_verification.enabled | BB_EMAIL_VERIFICATION_ENABLED | true | Require email verification on registration |
email_verification.ttl | BB_EMAIL_VERIFICATION_TTL | 24h | Verification link lifetime |
Logging & Metrics
| Key | Env Var | Default | Description |
|---|---|---|---|
logging.level | LOG_LEVEL | info | Log level (debug, info, warn, error) |
logging.format | LOG_FORMAT | json | Log format (json, text) |
metrics.enabled | BB_METRICS_ENABLED | true | Enable Prometheus metrics |
metrics.path | BB_METRICS_PATH | /metrics | Metrics endpoint path |
metrics.token | BB_METRICS_TOKEN | (empty) | When set, /metrics also requires Authorization: Bearer <token>. The loopback/private-address check alone depends on your proxy overwriting inbound X-Forwarded-For and X-Real-IP; a token does not. Prometheus sends it via bearer_token |
Security
| Key | Env Var | Default | Description |
|---|---|---|---|
security.allow_token_query_param | BB_SECURITY_ALLOW_TOKEN_QUERY_PARAM | false | Re-enables ?token=<jwt> on the WebSocket endpoints. The browser client uses the one-time /ws/ticket flow instead; a token in a URL is captured by reverse-proxy access logs and browser history |
security.require_email_verification | BB_SECURITY_REQUIRE_EMAIL_VERIFICATION | false | Refuse password logins until the address is confirmed. Off by default because enabling it on an existing deployment locks out every account created beforehand. The check runs after the password, so it never reveals whether an address exists |
security.enforce_sso_for_system_admins | BB_SECURITY_ENFORCE_SSO_FOR_SYSTEM_ADMINS | false | Require SSO for accounts carrying is_system_admin. The org-level enforce_sso setting joins through org membership, so it never covers a system admin who belongs to no organisation — the account that can grant is_system_admin, transfer teams and read every audit trail. Enforced only once that admin has a linked SSO identity, so it cannot lock out the last administrator |
Auth Cookie
| Key | Env Var | Default | Description |
|---|---|---|---|
auth_cookie.same_site | BB_AUTH_COOKIE_SAME_SITE | lax | lax, strict, or none. none forces Secure and therefore requires HTTPS — use it only when the frontend and API sit on unrelated domains. |
Public Demo Inbox
Off by default. When enabled, logged-out visitors can create a real short-lived inbox from the landing page. See Demo Mode.
| Key | Env Var | Default | Description |
|---|---|---|---|
demo.enabled | DEMO_ENABLED | false | Enable the public "try it" inbox |
demo.assignment_id | DEMO_ASSIGNMENT_ID | — | Domain assignment the demo user's team owns |
demo.user_id | DEMO_USER_ID | — | Demo account that owns created inboxes |
demo.ttl | DEMO_TTL | 10m | Lifetime of each demo inbox |
A malformed UUID in either ID logs a warning and leaves the demo disabled rather than failing the boot.
Config File Location
BB_CONFIG_PATH overrides the search path entirely and points at a single file.
It is also where the first-run web installer writes, so reads and writes stay
consistent. Without it, config.yaml is searched in . then /etc/burnerbyte/.