diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md
index eefdab926c..ac1b55f88f 100644
--- a/docs/my-website/docs/proxy/config_settings.md
+++ b/docs/my-website/docs/proxy/config_settings.md
@@ -819,6 +819,8 @@ router_settings:
| SMTP_SENDER_LOGO | Logo used in emails sent via SMTP
| SMTP_TLS | Flag to enable or disable TLS for SMTP connections
| SMTP_USERNAME | Username for SMTP authentication (do not set if SMTP does not require auth)
+| SENDGRID_API_KEY | API key for SendGrid email service
+| SENDGRID_SENDER_EMAIL | Email address used as the sender in SendGrid email transactions
| SPEND_LOGS_URL | URL for retrieving spend logs
| SPEND_LOG_CLEANUP_BATCH_SIZE | Number of logs deleted per batch during cleanup. Default is 1000
| SSL_CERTIFICATE | Path to the SSL certificate file
diff --git a/docs/my-website/docs/proxy/email.md b/docs/my-website/docs/proxy/email.md
index da8fc57dee..e50cc47f5d 100644
--- a/docs/my-website/docs/proxy/email.md
+++ b/docs/my-website/docs/proxy/email.md
@@ -68,6 +68,23 @@ litellm_settings:
callbacks: ["resend_email"]
```
+
+
+
+Add `sendgrid_email` to your proxy config.yaml under `litellm_settings`
+
+set the following env variables
+
+```shell showLineNumbers
+SENDGRID_API_KEY="SG.1234"
+SENDGRID_SENDER_EMAIL="notifications@your-domain.com"
+```
+
+```yaml showLineNumbers title="proxy_config.yaml"
+litellm_settings:
+ callbacks: ["sendgrid_email"]
+```
+
diff --git a/enterprise/dist/litellm_enterprise-0.1.24-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.24-py3-none-any.whl
new file mode 100644
index 0000000000..a26b0458c9
Binary files /dev/null and b/enterprise/dist/litellm_enterprise-0.1.24-py3-none-any.whl differ
diff --git a/enterprise/dist/litellm_enterprise-0.1.24.tar.gz b/enterprise/dist/litellm_enterprise-0.1.24.tar.gz
new file mode 100644
index 0000000000..4361910f4b
Binary files /dev/null and b/enterprise/dist/litellm_enterprise-0.1.24.tar.gz differ
diff --git a/enterprise/dist/litellm_enterprise-0.1.25-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.25-py3-none-any.whl
new file mode 100644
index 0000000000..bcc559d21b
Binary files /dev/null and b/enterprise/dist/litellm_enterprise-0.1.25-py3-none-any.whl differ
diff --git a/enterprise/dist/litellm_enterprise-0.1.25.tar.gz b/enterprise/dist/litellm_enterprise-0.1.25.tar.gz
new file mode 100644
index 0000000000..4db1cf7ef5
Binary files /dev/null and b/enterprise/dist/litellm_enterprise-0.1.25.tar.gz differ
diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.py b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.py
new file mode 100644
index 0000000000..dfde9ce329
--- /dev/null
+++ b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.py
@@ -0,0 +1,81 @@
+"""
+LiteLLM x SendGrid email integration.
+
+Docs: https://docs.sendgrid.com/api-reference/mail-send/mail-send
+"""
+
+import os
+from typing import List
+
+from litellm._logging import verbose_logger
+from litellm.llms.custom_httpx.http_handler import (
+ get_async_httpx_client,
+ httpxSpecialProvider,
+)
+
+from .base_email import BaseEmailLogger
+
+
+SENDGRID_API_ENDPOINT = "https://api.sendgrid.com/v3/mail/send"
+
+
+class SendGridEmailLogger(BaseEmailLogger):
+ """
+ Send emails using SendGrid's Mail Send API.
+
+ Required env vars:
+ - SENDGRID_API_KEY
+ """
+
+ def __init__(self):
+ self.async_httpx_client = get_async_httpx_client(
+ llm_provider=httpxSpecialProvider.LoggingCallback
+ )
+ self.sendgrid_api_key = os.getenv("SENDGRID_API_KEY")
+ self.sendgrid_sender_email = os.getenv("SENDGRID_SENDER_EMAIL")
+ verbose_logger.debug("SendGrid Email Logger initialized.")
+
+ async def send_email(
+ self,
+ from_email: str,
+ to_email: List[str],
+ subject: str,
+ html_body: str,
+ ):
+ """
+ Send an email via SendGrid.
+ """
+ if not self.sendgrid_api_key:
+ raise ValueError("SENDGRID_API_KEY is not set")
+
+ sender_email = self.sendgrid_sender_email or from_email
+ verbose_logger.debug(
+ f"Sending email via SendGrid from {sender_email} to {to_email} with subject {subject}"
+ )
+
+ payload = {
+ "from": {"email": sender_email},
+ "personalizations": [
+ {
+ "to": [{"email": email} for email in to_email],
+ "subject": subject,
+ }
+ ],
+ "content": [
+ {
+ "type": "text/html",
+ "value": html_body,
+ }
+ ],
+ }
+
+ response = await self.async_httpx_client.post(
+ url=SENDGRID_API_ENDPOINT,
+ json=payload,
+ headers={"Authorization": f"Bearer {self.sendgrid_api_key}"},
+ )
+
+ verbose_logger.debug(
+ f"SendGrid response status={response.status_code}, body={response.text}"
+ )
+ return
\ No newline at end of file
diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml
index 2305a5e635..2bcd8d33ad 100644
--- a/enterprise/pyproject.toml
+++ b/enterprise/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "litellm-enterprise"
-version = "0.1.23"
+version = "0.1.25"
description = "Package for LiteLLM Enterprise features"
authors = ["BerriAI"]
readme = "README.md"
@@ -22,7 +22,7 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.commitizen]
-version = "0.1.23"
+version = "0.1.25"
version_files = [
"pyproject.toml:version",
"../requirements.txt:litellm-enterprise==",
diff --git a/litellm/__init__.py b/litellm/__init__.py
index e9bfed2ed1..2c6f04a3ae 100644
--- a/litellm/__init__.py
+++ b/litellm/__init__.py
@@ -159,6 +159,7 @@ _custom_logger_compatible_callbacks_literal = Literal[
"anthropic_cache_control_hook",
"generic_api",
"resend_email",
+ "sendgrid_email",
"smtp_email",
"deepeval",
"s3_v2",
diff --git a/litellm/litellm_core_utils/custom_logger_registry.py b/litellm/litellm_core_utils/custom_logger_registry.py
index fdc9f37455..fa2ff42e1d 100644
--- a/litellm/litellm_core_utils/custom_logger_registry.py
+++ b/litellm/litellm_core_utils/custom_logger_registry.py
@@ -102,6 +102,9 @@ class CustomLoggerRegistry:
from litellm_enterprise.enterprise_callbacks.send_emails.resend_email import (
ResendEmailLogger,
)
+ from litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email import (
+ SendGridEmailLogger,
+ )
from litellm_enterprise.enterprise_callbacks.send_emails.smtp_email import (
SMTPEmailLogger,
)
@@ -114,6 +117,7 @@ class CustomLoggerRegistry:
"pagerduty": PagerDutyAlerting,
"generic_api": GenericAPILogger,
"resend_email": ResendEmailLogger,
+ "sendgrid_email": SendGridEmailLogger,
"smtp_email": SMTPEmailLogger,
}
CALLBACK_CLASS_STR_TO_CLASS_TYPE.update(enterprise_loggers)
diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py
index 6e845c56b5..90edc6ec05 100644
--- a/litellm/litellm_core_utils/litellm_logging.py
+++ b/litellm/litellm_core_utils/litellm_logging.py
@@ -172,6 +172,9 @@ try:
from litellm_enterprise.enterprise_callbacks.send_emails.resend_email import (
ResendEmailLogger,
)
+ from litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email import (
+ SendGridEmailLogger,
+ )
from litellm_enterprise.enterprise_callbacks.send_emails.smtp_email import (
SMTPEmailLogger,
)
@@ -190,6 +193,7 @@ except Exception as e:
)
GenericAPILogger = CustomLogger # type: ignore
ResendEmailLogger = CustomLogger # type: ignore
+ SendGridEmailLogger = CustomLogger # type: ignore
SMTPEmailLogger = CustomLogger # type: ignore
PagerDutyAlerting = CustomLogger # type: ignore
EnterpriseCallbackControls = None # type: ignore
@@ -3904,6 +3908,13 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915
resend_email_logger = ResendEmailLogger()
_in_memory_loggers.append(resend_email_logger)
return resend_email_logger # type: ignore
+ elif logging_integration == "sendgrid_email":
+ for callback in _in_memory_loggers:
+ if isinstance(callback, SendGridEmailLogger):
+ return callback
+ sendgrid_email_logger = SendGridEmailLogger()
+ _in_memory_loggers.append(sendgrid_email_logger)
+ return sendgrid_email_logger # type: ignore
elif logging_integration == "smtp_email":
for callback in _in_memory_loggers:
if isinstance(callback, SMTPEmailLogger):
@@ -4144,6 +4155,10 @@ def get_custom_logger_compatible_class( # noqa: PLR0915
for callback in _in_memory_loggers:
if isinstance(callback, ResendEmailLogger):
return callback
+ elif logging_integration == "sendgrid_email":
+ for callback in _in_memory_loggers:
+ if isinstance(callback, SendGridEmailLogger):
+ return callback
elif logging_integration == "smtp_email":
for callback in _in_memory_loggers:
if isinstance(callback, SMTPEmailLogger):
diff --git a/poetry.lock b/poetry.lock
index 726033e1d6..f6d63506a4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2720,13 +2720,13 @@ files = [
[[package]]
name = "litellm-enterprise"
-version = "0.1.23"
+version = "0.1.24"
description = "Package for LiteLLM Enterprise features"
optional = true
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
files = [
- {file = "litellm_enterprise-0.1.23-py3-none-any.whl", hash = "sha256:d803ce3ef79494f21447368f1f4e05669183714e5081da9c27a05b1770eb1422"},
- {file = "litellm_enterprise-0.1.23.tar.gz", hash = "sha256:0171e1d10c10b29e663d03a6b84c77465e58fd1923ecd0f89796622ffb5c7bb0"},
+ {file = "litellm_enterprise-0.1.24-py3-none-any.whl", hash = "sha256:82548d0377282c8491d695e6b891e0930910ab410ac10f01773c13c263ecef3f"},
+ {file = "litellm_enterprise-0.1.24.tar.gz", hash = "sha256:e009b9e1be09735c58458b356a9d2b942f468b4a934c0cb6ace8c43c6f43ba0f"},
]
[[package]]
@@ -6973,4 +6973,4 @@ utils = ["numpydoc"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4.0"
-content-hash = "fec0ac9f9222e9952c6244bf874fac20201ac1e14e435d3201611ab4f882c4d7"
+content-hash = "ddc452ea7bacb386fe494f5a2b8f6bfa4715eb0a16ce43c23cff731076b2cc67"
diff --git a/pyproject.toml b/pyproject.toml
index ab67697465..8093ad9876 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -61,7 +61,7 @@ redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.
mcp = {version = "^1.21.2", optional = true, python = ">=3.10"}
litellm-proxy-extras = {version = "0.4.12", optional = true}
rich = {version = "13.7.1", optional = true}
-litellm-enterprise = {version = "0.1.23", optional = true}
+litellm-enterprise = {version = "0.1.25", optional = true}
diskcache = {version = "^5.6.1", optional = true}
polars = {version = "^1.31.0", optional = true, python = ">=3.10"}
semantic-router = {version = ">=0.1.12", optional = true, python = ">=3.9,<3.14"}
diff --git a/requirements.txt b/requirements.txt
index 604e58132f..0293d5579a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -64,4 +64,4 @@ soundfile==0.12.1 # for audio file processing
########################
# LITELLM ENTERPRISE DEPENDENCIES
########################
-litellm-enterprise==0.1.23
+litellm-enterprise==0.1.25
diff --git a/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py
new file mode 100644
index 0000000000..4ecb4872aa
--- /dev/null
+++ b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py
@@ -0,0 +1,99 @@
+import os
+import sys
+import unittest.mock as mock
+
+import pytest
+from httpx import Response
+
+sys.path.insert(0, os.path.abspath("../../.."))
+
+from litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email import (
+ SendGridEmailLogger,
+)
+
+
+@pytest.fixture
+def mock_env_vars():
+ with mock.patch.dict(os.environ, {"SENDGRID_API_KEY": "test_api_key"}):
+ yield
+
+
+@pytest.fixture
+def mock_httpx_client():
+ with mock.patch(
+ "litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email.get_async_httpx_client"
+ ) as mock_client:
+ mock_response = mock.AsyncMock(spec=Response)
+ mock_response.status_code = 202
+ mock_response.text = "accepted"
+
+ mock_async_client = mock.AsyncMock()
+ mock_async_client.post.return_value = mock_response
+ mock_client.return_value = mock_async_client
+
+ yield mock_async_client
+
+
+@pytest.mark.asyncio
+async def test_send_email_success(mock_env_vars, mock_httpx_client):
+ logger = SendGridEmailLogger()
+
+ from_email = "test@example.com"
+ to_email = ["recipient@example.com"]
+ subject = "Test Subject"
+ html_body = "
Test email body
"
+
+ await logger.send_email(
+ from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
+ )
+
+ mock_httpx_client.post.assert_called_once()
+ call_args = mock_httpx_client.post.call_args
+ assert call_args[1]["url"] == "https://api.sendgrid.com/v3/mail/send"
+
+ payload = call_args[1]["json"]
+ assert payload["from"] == {"email": from_email}
+ assert payload["personalizations"][0]["to"] == [{"email": to_email[0]}]
+ assert payload["personalizations"][0]["subject"] == subject
+ assert payload["content"][0]["type"] == "text/html"
+ assert payload["content"][0]["value"] == html_body
+
+ assert call_args[1]["headers"] == {"Authorization": "Bearer test_api_key"}
+
+
+@pytest.mark.asyncio
+async def test_send_email_missing_api_key(mock_httpx_client):
+ with mock.patch.dict(os.environ, {}, clear=True):
+ logger = SendGridEmailLogger()
+
+ with pytest.raises(ValueError):
+ await logger.send_email(
+ from_email="test@example.com",
+ to_email=["recipient@example.com"],
+ subject="Test Subject",
+ html_body="Test email body
",
+ )
+
+ mock_httpx_client.post.assert_not_called()
+
+
+@pytest.mark.asyncio
+async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
+ logger = SendGridEmailLogger()
+
+ from_email = "test@example.com"
+ to_email = ["recipient1@example.com", "recipient2@example.com"]
+ subject = "Test Subject"
+ html_body = "Test email body
"
+
+ await logger.send_email(
+ from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
+ )
+
+ mock_httpx_client.post.assert_called_once()
+ payload = mock_httpx_client.post.call_args[1]["json"]
+
+ assert payload["personalizations"][0]["to"] == [
+ {"email": "recipient1@example.com"},
+ {"email": "recipient2@example.com"},
+ ]