diff --git a/litellm/proxy/config_resolvers/alerting.py b/litellm/proxy/config_resolvers/alerting.py index 3704ec09355..292d043e99c 100644 --- a/litellm/proxy/config_resolvers/alerting.py +++ b/litellm/proxy/config_resolvers/alerting.py @@ -12,6 +12,7 @@ EMAIL_DESCRIPTORS: tuple[FieldDescriptor, ...] = ( FieldDescriptor("SMTP_HOST", "SMTP_HOST", "SMTP_HOST"), FieldDescriptor("SMTP_PORT", "SMTP_PORT", "SMTP_PORT", default="587"), FieldDescriptor("SMTP_TLS", "SMTP_TLS", "SMTP_TLS", default="True"), + FieldDescriptor("SMTP_USE_SSL", "SMTP_USE_SSL", "SMTP_USE_SSL", default="False"), FieldDescriptor("SMTP_USERNAME", "SMTP_USERNAME", "SMTP_USERNAME", is_secret=True), FieldDescriptor("SMTP_PASSWORD", "SMTP_PASSWORD", "SMTP_PASSWORD", is_secret=True), FieldDescriptor("SMTP_SENDER_EMAIL", "SMTP_SENDER_EMAIL", "SMTP_SENDER_EMAIL"), diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index ab64224fdc3..7beff719468 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -1218,6 +1218,7 @@ def test_get_config_returns_email_settings_set_only_in_process_env(monkeypatch): monkeypatch.setenv("SMTP_HOST", "smtp.env-host.com") monkeypatch.setenv("SMTP_PORT", "2525") monkeypatch.setenv("SMTP_TLS", "False") + monkeypatch.setenv("SMTP_USE_SSL", "True") monkeypatch.setenv("SMTP_USERNAME", "env-user") monkeypatch.setenv("SMTP_PASSWORD", smtp_password) monkeypatch.setenv("SMTP_SENDER_EMAIL", "alerts@env-host.com") @@ -1236,6 +1237,7 @@ def test_get_config_returns_email_settings_set_only_in_process_env(monkeypatch): assert variables["SMTP_HOST"] == "smtp.env-host.com" assert variables["SMTP_PORT"] == "2525" assert variables["SMTP_TLS"] == "False" + assert variables["SMTP_USE_SSL"] == "True" assert variables["SMTP_USERNAME"] == "env-user" assert variables["SMTP_SENDER_EMAIL"] == "alerts@env-host.com" assert variables["TEST_EMAIL_ADDRESS"] == "admin@env-host.com" @@ -1269,7 +1271,15 @@ def test_get_config_email_settings_prefer_stored_over_process_env(monkeypatch): def test_get_config_email_settings_absent_everywhere_stay_none(monkeypatch): """A field set in neither source is reported unset rather than invented.""" - for var in ("SMTP_HOST", "SMTP_PORT", "SMTP_TLS", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_SENDER_EMAIL"): + for var in ( + "SMTP_HOST", + "SMTP_PORT", + "SMTP_TLS", + "SMTP_USE_SSL", + "SMTP_USERNAME", + "SMTP_PASSWORD", + "SMTP_SENDER_EMAIL", + ): monkeypatch.delenv(var, raising=False) variables = _get_email_alert_variables( diff --git a/ui/litellm-dashboard/src/components/email_settings.test.tsx b/ui/litellm-dashboard/src/components/email_settings.test.tsx index bd09ca0abc4..1f826857b48 100644 --- a/ui/litellm-dashboard/src/components/email_settings.test.tsx +++ b/ui/litellm-dashboard/src/components/email_settings.test.tsx @@ -22,7 +22,12 @@ const alerts = [ variables: { SMTP_HOST: "smtp.example.com", SMTP_PORT: "587", + SMTP_TLS: "True", + SMTP_USE_SSL: "False", + SMTP_USERNAME: "smtp-user", SMTP_PASSWORD: "********", + SMTP_SENDER_EMAIL: "alerts@example.com", + TEST_EMAIL_ADDRESS: "admin@example.com", EMAIL_LOGO_URL: "https://example.com/logo.png", }, }, @@ -53,7 +58,15 @@ describe("EmailSettings", () => { expect(inputNamed("SMTP_HOST")).toHaveValue("smtp.example.com"); expect(inputNamed("SMTP_PORT")).toHaveValue("587"); + expect(inputNamed("SMTP_PORT")).toHaveAttribute("type", "number"); + expect(inputNamed("SMTP_HOST")).toHaveAttribute("type", "text"); + expect(inputNamed("SMTP_TLS")).toHaveAttribute("type", "text"); + expect(inputNamed("SMTP_USE_SSL")).toHaveAttribute("type", "text"); + expect(inputNamed("SMTP_USERNAME")).toHaveAttribute("type", "text"); expect(inputNamed("SMTP_PASSWORD")).toHaveValue("********"); + expect(inputNamed("SMTP_SENDER_EMAIL")).toHaveAttribute("type", "text"); + expect(inputNamed("TEST_EMAIL_ADDRESS")).toHaveAttribute("type", "text"); + expect(inputNamed("EMAIL_LOGO_URL")).toHaveAttribute("type", "text"); expect(document.querySelector('input[name="SLACK_WEBHOOK_URL"]')).toBeNull(); }); @@ -63,6 +76,21 @@ describe("EmailSettings", () => { expect(screen.getByText("SMTP_HOST")).toBeInTheDocument(); expect(screen.getByText(/Enter the SMTP host address/)).toBeInTheDocument(); expect(screen.getByText(/Enter the SMTP port number/)).toBeInTheDocument(); + expect(screen.getByText(/Optional SMTP username/)).toBeInTheDocument(); + expect(screen.getByText(/Optional SMTP password/)).toBeInTheDocument(); + }); + + it("only requires the SMTP host and sender email", () => { + renderWithProviders(); + + expect(inputNamed("SMTP_HOST")).toBeRequired(); + expect(inputNamed("SMTP_SENDER_EMAIL")).toBeRequired(); + expect(inputNamed("SMTP_PORT")).not.toBeRequired(); + expect(inputNamed("SMTP_TLS")).not.toBeRequired(); + expect(inputNamed("SMTP_USE_SSL")).not.toBeRequired(); + expect(inputNamed("SMTP_USERNAME")).not.toBeRequired(); + expect(inputNamed("SMTP_PASSWORD")).not.toBeRequired(); + expect(inputNamed("TEST_EMAIL_ADDRESS")).not.toBeRequired(); }); it("submits only the fields the admin actually edited", async () => { diff --git a/ui/litellm-dashboard/src/components/email_settings.tsx b/ui/litellm-dashboard/src/components/email_settings.tsx index 85fb4ce1ca3..0cfac8107ad 100644 --- a/ui/litellm-dashboard/src/components/email_settings.tsx +++ b/ui/litellm-dashboard/src/components/email_settings.tsx @@ -14,13 +14,22 @@ interface EmailSettingsProps { const REQUIRED_MARKER = Required * ; +const REQUIRED_FIELDS = new Set(["SMTP_HOST", "SMTP_SENDER_EMAIL"]); + +const FIELD_TYPES: Record = { + SMTP_PORT: "number", + SMTP_PASSWORD: "password", +}; + const FIELD_HELP: Record = { SMTP_HOST: <>Enter the SMTP host address, e.g. `smtp.resend.com`{REQUIRED_MARKER}, - SMTP_PORT: <>Enter the SMTP port number, e.g. `587`{REQUIRED_MARKER}, - SMTP_USERNAME: <>Enter the SMTP username, e.g. `username`{REQUIRED_MARKER}, - SMTP_PASSWORD: REQUIRED_MARKER, + SMTP_PORT: <>Enter the SMTP port number, e.g. `587`, + SMTP_TLS: <>Use STARTTLS for the SMTP connection. Defaults to `True`, + SMTP_USE_SSL: <>Use implicit SSL for the SMTP connection. Defaults to `False`, + SMTP_USERNAME: <>Optional SMTP username for authenticated servers, + SMTP_PASSWORD: <>Optional SMTP password for authenticated servers, SMTP_SENDER_EMAIL: <>Enter the sender email address, e.g. `sender@berri.ai`{REQUIRED_MARKER}, - TEST_EMAIL_ADDRESS: <>Email Address to send `Test Email Alert` to. example: `info@berri.ai`{REQUIRED_MARKER}, + TEST_EMAIL_ADDRESS: <>Optional address for the `Test Email Alert` action, EMAIL_LOGO_URL: <>(Optional) Customize the Logo that appears in the email, pass a url to your logo, EMAIL_SUPPORT_CONTACT: ( <>(Optional) Customize the support email address that appears in the email. Default is support@berri.ai @@ -115,8 +124,9 @@ const EmailSettings: React.FC = ({ accessToken, premiumUser, )}