feat(admin): move email/SMTP config into DB admin settings (BUNYIP-351) #351
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-351-email-db-config"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 2 of BUNYIP-351: move the email / SMTP settings out of env-only vars and into a DB-persisted, admin-editable singleton, following the established
tier_config/stripe_configDB-overrides-env pattern.SMTP_HOST,SMTP_PORT,SMTP_TLS,SMTP_USERNAME,SMTP_PASSWORD,SMTP_FROM(from_email + from_name),EMAIL_ENABLED, andADMIN_NOTIFICATION_EMAILSremain the bootstrap defaults. Once an admin edits them in the new Email settings page, theemail_configrow overrides them per field and applies immediately with no restart. A wiped DB (all-NULL row) falls straight back to the env defaults, so a fresh deployment boots identically.How
email_config(id CHECK=1, nullable columns,updated_at/updated_by). Additive and immutable.smtp_passwordBYTEA +smtp_password_nonce+key_version) with the sameEncryptionKeySetthat guards the Stripe secrets (STRIPE_ENCRYPTION_KEY). It is never stored or returned in plaintext; the admin read API returns only a masked hint +has_smtp_password, exactly like the Stripe keys.EmailConfigRow+EmailConfigResponsemodel;EmailConfigRepository(get+ COALESCE partialupdate+clear_password).EmailConfig::from_db_row/has_db_overridesmerge (mirrorsTierConfig). System-level fields (base_url,app_name) and the dev-onlylog_tokensgate stay env-derived;enabledis recomputed from the resolved host so the env semantics hold against DB config.EmailServicenow holds its transport + config behind astd::sync::RwLockwith areload()that rebuilds the transport (host/port/tls/credentials are baked into it), mirroringStripeService. Everysend_*path snapshots the config/transport under a short read lock, never held across.await.main.rs, resolved with the Stripe key set.GET/PUT /v1/admin/emailhandlers, routes, and anAdminEmailConfigUpdatedaudit action (the password is never logged, onlypassword_changed: bool); the PUT hot-reloads the running service./admin/email(api client, types DTO, page + save handler,mailicon)..env.exampledocuments the vars as bootstrap defaults now overridable in the admin UI.Production safety (BUNYIP-204)
The fail-fast is preserved against DB-sourced config: the effective (DB-over-env) email config is checked at startup (panic) and on admin update (validation error) so a production deployment can never be left silently running with email disabled.
Tests
cargo fmt --check,cargo clippy --workspace --all-targets -D warnings, andcargo test --workspace --all-targetsall green in the pinned rust-builder container. New unit tests coverfrom_db_row(env fallback, overrides, encrypted-password decrypt round-trip, out-of-range port) and the web form body builder (enabled always sent, blanks omitted, port/email validation).Scope
Auto-ban (phase 1) is a separate PR. The leftover Stripe checkout knobs (phase 3:
STRIPE_SUCCESS_URL/STRIPE_CANCEL_URL/ trial days into the existingstripe_config) follow next. Stripe secrets and tier config were already DB-backed.🤖 Generated with Claude Code
https://claude.ai/code/session_01CTpxCd1SvpE8sk82aGikkz