Litellm emails smtp fixes (#10730)

* feat: smtp email logger

* feat: smtp email logger

* fix: linting errors
This commit is contained in:
Ishaan Jaff 2025-05-10 17:23:59 -07:00 • committed by GitHub
parent 037b2f9033
commit 07cb100fdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 91 additions and 7 deletions

View file

@ -0,0 +1,47 @@
"""
This is the litellm SMTP email integration
"""
import asyncio
from typing import List
from litellm._logging import verbose_logger
from .base_email import BaseEmailLogger
class SMTPEmailLogger(BaseEmailLogger):
"""
This is the litellm SMTP email integration
Required SMTP environment variables:
- SMTP_HOST
- SMTP_PORT
- SMTP_USERNAME
- SMTP_PASSWORD
- SMTP_SENDER_EMAIL
"""
def __init__(self):
verbose_logger.debug("SMTP Email Logger initialized....")
async def send_email(
self,
from_email: str,
to_email: List[str],
subject: str,
html_body: str,
):
from litellm.proxy.utils import send_email as send_smtp_email
verbose_logger.debug(
f"Sending email from {from_email} to {to_email} with subject {subject}"
)
for receiver_email in to_email:
asyncio.create_task(
send_smtp_email(
receiver_email=receiver_email,
subject=subject,
html=html_body,
)
)
return

View file

@ -117,6 +117,7 @@ _custom_logger_compatible_callbacks_literal = Literal[
"bedrock_vector_store",
"generic_api",
"resend_email",
"smtp_email",
]
logged_real_time_event_types: Optional[Union[List[str], Literal["*"]]] = None
_known_custom_logger_compatible_callbacks: List = list(

View file

@ -140,13 +140,16 @@ try:
from litellm_enterprise.enterprise_callbacks.send_emails.resend_email import (
ResendEmailLogger,
)
from litellm_enterprise.enterprise_callbacks.send_emails.smtp_email import (
SMTPEmailLogger,
)
except Exception as e:
verbose_logger.debug(
f"[Non-Blocking] Unable to import GenericAPILogger - LiteLLM Enterprise Feature - {str(e)}"
)
GenericAPILogger = CustomLogger # type: ignore
ResendEmailLogger = CustomLogger # type: ignore
SMTPEmailLogger = CustomLogger # type: ignore
_in_memory_loggers: List[Any] = []
### GLOBAL VARIABLES ###
@ -3053,6 +3056,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 == "smtp_email":
for callback in _in_memory_loggers:
if isinstance(callback, SMTPEmailLogger):
return callback
smtp_email_logger = SMTPEmailLogger()
_in_memory_loggers.append(smtp_email_logger)
return smtp_email_logger # type: ignore
elif logging_integration == "humanloop":
for callback in _in_memory_loggers:
if isinstance(callback, HumanloopLogger):
@ -3204,6 +3214,10 @@ def get_custom_logger_compatible_class( # noqa: PLR0915
for callback in _in_memory_loggers:
if isinstance(callback, ResendEmailLogger):
return callback
elif logging_integration == "smtp_email":
for callback in _in_memory_loggers:
if isinstance(callback, SMTPEmailLogger):
return callback
return None
except Exception as e:

View file

@ -6,7 +6,7 @@ model_list:
litellm_settings:
callbacks: ["resend_email"]
callbacks: ["smtp_email"]
guardrails:
- guardrail_name: "bedrock-pre-guard"

View file

@ -2362,7 +2362,11 @@ async def _cache_user_row(user_id: str, cache: DualCache, db: PrismaClient):
return
async def send_email(receiver_email, subject, html):
async def send_email(
receiver_email: Optional[str] = None,
subject: Optional[str] = None,
html: Optional[str] = None,
):
"""
smtp_host,
smtp_port,
@ -2380,6 +2384,12 @@ async def send_email(receiver_email, subject, html):
sender_email = os.getenv("SMTP_SENDER_EMAIL", None)
if sender_email is None:
raise ValueError("Trying to use SMTP, but SMTP_SENDER_EMAIL is not set")
if receiver_email is None:
raise ValueError(f"No receiver email provided for SMTP email. {receiver_email}")
if subject is None:
raise ValueError(f"No subject provided for SMTP email. {subject}")
if html is None:
raise ValueError(f"No HTML body provided for SMTP email. {html}")
## EMAIL SETUP ##
email_message = MIMEMultipart()
@ -2398,19 +2408,31 @@ async def send_email(receiver_email, subject, html):
try:
# Establish a secure connection with the SMTP server
with smtplib.SMTP(smtp_host, smtp_port) as server: # type: ignore
with smtplib.SMTP(
host=smtp_host,
port=smtp_port,
) as server:
if os.getenv("SMTP_TLS", "True") != "False":
server.starttls()
# Login to your email account only if smtp_username and smtp_password are provided
if smtp_username and smtp_password:
server.login(smtp_username, smtp_password) # type: ignore
server.login(
user=smtp_username,
password=smtp_password,
)
# Send the email
server.send_message(email_message)
server.send_message(
msg=email_message,
from_addr=sender_email,
to_addrs=receiver_email,
)
except Exception as e:
print_verbose("An error occurred while sending the email:" + str(e))
verbose_proxy_logger.exception(
"An error occurred while sending the email:" + str(e)
)
def hash_token(token: str):