From 687b1c18690f92065c258a36012938cf46877053 Mon Sep 17 00:00:00 2001 From: yangdx Date: Thu, 26 Mar 2026 14:15:10 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(health=5Fendpoints):=20resol?= =?UTF-8?q?ve=20TEST=5FEMAIL=5FADDRESS=20not=20read=20from=20DB=20in=20ema?= =?UTF-8?q?il=20health=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetch fresh decrypted config from DB when store_model_in_db is enabled so TEST_EMAIL_ADDRESS is correctly resolved in DB mode - fall back to os.getenv for YAML / env-var deployments (no-op path) - add silent mode to handleSaveEmailSettings to suppress notifications when called programmatically from the test email flow - silently persist form values before triggering email health check so the backend can read TEST_EMAIL_ADDRESS from DB (DB mode) - swallow errors in silent mode to allow test flow to proceed using env-var / YAML config values as fallback --- .../health_endpoints/_health_endpoints.py | 18 +++++++++++++++++- .../src/components/email_settings.tsx | 18 +++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index ef9436f2d8c..4fa24143c9d 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -438,6 +438,22 @@ async def health_services_endpoint( # noqa: PLR0915 }, ) if service == "email": + from litellm.proxy.proxy_server import proxy_config, store_model_in_db + + # TEST_EMAIL_ADDRESS is stored encrypted in the DB when the proxy + # is running in DB mode. Calling get_config() ensures the value is + # freshly decrypted and available both in the returned config dict + # and in os.environ. For YAML / env-var deployments the call is a + # cheap no-op and os.getenv() still works as the fallback. + if store_model_in_db and prisma_client is not None: + _fresh_config = await proxy_config.get_config() + _env_vars = _fresh_config.get("environment_variables", {}) + _test_email_address = _env_vars.get( + "TEST_EMAIL_ADDRESS" + ) or os.getenv("TEST_EMAIL_ADDRESS") + else: + _test_email_address = os.getenv("TEST_EMAIL_ADDRESS") + webhook_event = WebhookEvent( event="key_created", event_group=Litellm_EntityType.KEY, @@ -447,7 +463,7 @@ async def health_services_endpoint( # noqa: PLR0915 spend=0, max_budget=0, user_id=user_api_key_dict.user_id, - user_email=os.getenv("TEST_EMAIL_ADDRESS"), + user_email=_test_email_address, team_id=user_api_key_dict.team_id, ) diff --git a/ui/litellm-dashboard/src/components/email_settings.tsx b/ui/litellm-dashboard/src/components/email_settings.tsx index 53d08ad64f5..8fd3a37398c 100644 --- a/ui/litellm-dashboard/src/components/email_settings.tsx +++ b/ui/litellm-dashboard/src/components/email_settings.tsx @@ -14,7 +14,7 @@ interface EmailSettingsProps { } const EmailSettings: React.FC = ({ accessToken, premiumUser, alerts }) => { - const handleSaveEmailSettings = async () => { + const handleSaveEmailSettings = async ({ silent = false }: { silent?: boolean } = {}) => { if (!accessToken) { return; } @@ -43,9 +43,15 @@ const EmailSettings: React.FC = ({ accessToken, premiumUser, }; try { await setCallbacksCall(accessToken, payload); - NotificationManager.success("Email settings updated successfully"); + if (!silent) { + NotificationManager.success("Email settings updated successfully"); + } } catch (error) { - NotificationManager.fromBackend(error); + if (!silent) { + NotificationManager.fromBackend(error); + } + // In silent mode (called from test flow) swallow the error so that + // the test can still proceed using env-var / YAML config values. } }; @@ -163,6 +169,12 @@ const EmailSettings: React.FC = ({ accessToken, premiumUser, onClick={async () => { if (!accessToken) return; try { + // Silently attempt to persist the current form values so the + // backend can read TEST_EMAIL_ADDRESS from the DB (DB mode). + // If saving is not supported (e.g. STORE_MODEL_IN_DB=False / + // YAML mode), this is a no-op and the backend will fall back to + // the TEST_EMAIL_ADDRESS environment variable instead. + await handleSaveEmailSettings({ silent: true }); await serviceHealthCheck(accessToken, "email"); NotificationManager.success("Email test triggered. Check your configured email inbox/logs."); } catch (error) {