Spaces:
Configuration error
Configuration error
""" | |
Base class for sending emails to user after creating keys or invite links | |
""" | |
import json | |
import os | |
from typing import List, Optional | |
from litellm_enterprise.types.enterprise_callbacks.send_emails import ( | |
EmailEvent, | |
EmailParams, | |
SendKeyCreatedEmailEvent, | |
) | |
from litellm._logging import verbose_proxy_logger | |
from litellm.integrations.custom_logger import CustomLogger | |
from litellm.integrations.email_templates.email_footer import EMAIL_FOOTER | |
from litellm.integrations.email_templates.key_created_email import ( | |
KEY_CREATED_EMAIL_TEMPLATE, | |
) | |
from litellm.integrations.email_templates.user_invitation_email import ( | |
USER_INVITATION_EMAIL_TEMPLATE, | |
) | |
from litellm.proxy._types import WebhookEvent | |
from litellm.types.integrations.slack_alerting import LITELLM_LOGO_URL | |
class BaseEmailLogger(CustomLogger): | |
DEFAULT_LITELLM_EMAIL = "[email protected]" | |
DEFAULT_SUPPORT_EMAIL = "[email protected]" | |
async def send_user_invitation_email(self, event: WebhookEvent): | |
""" | |
Send email to user after inviting them to the team | |
""" | |
email_params = await self._get_email_params( | |
email_event=EmailEvent.new_user_invitation, | |
user_id=event.user_id, | |
user_email=getattr(event, "user_email", None), | |
) | |
# Implement invitation email logic using email_params | |
verbose_proxy_logger.debug( | |
f"send_user_invitation_email_event: {json.dumps(event, indent=4, default=str)}" | |
) | |
email_html_content = USER_INVITATION_EMAIL_TEMPLATE.format( | |
email_logo_url=email_params.logo_url, | |
recipient_email=email_params.recipient_email, | |
base_url=email_params.base_url, | |
email_support_contact=email_params.support_contact, | |
email_footer=EMAIL_FOOTER, | |
) | |
await self.send_email( | |
from_email=self.DEFAULT_LITELLM_EMAIL, | |
to_email=[email_params.recipient_email], | |
subject=f"LiteLLM: {event.event_message}", | |
html_body=email_html_content, | |
) | |
pass | |
async def send_key_created_email( | |
self, send_key_created_email_event: SendKeyCreatedEmailEvent | |
): | |
""" | |
Send email to user after creating key for the user | |
""" | |
email_params = await self._get_email_params( | |
user_id=send_key_created_email_event.user_id, | |
user_email=send_key_created_email_event.user_email, | |
email_event=EmailEvent.virtual_key_created, | |
) | |
verbose_proxy_logger.debug( | |
f"send_key_created_email_event: {json.dumps(send_key_created_email_event, indent=4, default=str)}" | |
) | |
email_html_content = KEY_CREATED_EMAIL_TEMPLATE.format( | |
email_logo_url=email_params.logo_url, | |
recipient_email=email_params.recipient_email, | |
key_budget=self._format_key_budget(send_key_created_email_event.max_budget), | |
key_token=send_key_created_email_event.virtual_key, | |
base_url=email_params.base_url, | |
email_support_contact=email_params.support_contact, | |
email_footer=EMAIL_FOOTER, | |
) | |
await self.send_email( | |
from_email=self.DEFAULT_LITELLM_EMAIL, | |
to_email=[email_params.recipient_email], | |
subject=f"LiteLLM: {send_key_created_email_event.event_message}", | |
html_body=email_html_content, | |
) | |
pass | |
async def _get_email_params( | |
self, | |
email_event: EmailEvent, | |
user_id: Optional[str] = None, | |
user_email: Optional[str] = None, | |
) -> EmailParams: | |
""" | |
Get common email parameters used across different email sending methods | |
Returns: | |
EmailParams object containing logo_url, support_contact, base_url, and recipient_email | |
""" | |
logo_url = os.getenv("EMAIL_LOGO_URL", None) or LITELLM_LOGO_URL | |
support_contact = os.getenv("EMAIL_SUPPORT_CONTACT", self.DEFAULT_SUPPORT_EMAIL) | |
base_url = os.getenv("PROXY_BASE_URL", "http://0.0.0.0:4000") | |
recipient_email: Optional[ | |
str | |
] = user_email or await self._lookup_user_email_from_db(user_id=user_id) | |
if recipient_email is None: | |
raise ValueError( | |
f"User email not found for user_id: {user_id}. User email is required to send email." | |
) | |
# if user invited event then send invitation link | |
if email_event == EmailEvent.new_user_invitation: | |
base_url = await self._get_invitation_link( | |
user_id=user_id, base_url=base_url | |
) | |
return EmailParams( | |
logo_url=logo_url, | |
support_contact=support_contact, | |
base_url=base_url, | |
recipient_email=recipient_email, | |
) | |
def _format_key_budget(self, max_budget: Optional[float]) -> str: | |
""" | |
Format the key budget to be displayed in the email | |
""" | |
if max_budget is None: | |
return "No budget" | |
return f"${max_budget}" | |
async def _lookup_user_email_from_db(self, user_id: Optional[str]) -> Optional[str]: | |
""" | |
Lookup user email from user_id | |
""" | |
from litellm.proxy.proxy_server import prisma_client | |
if prisma_client is None: | |
verbose_proxy_logger.debug( | |
f"Prisma client not found. Unable to lookup user email for user_id: {user_id}" | |
) | |
return None | |
user_row = await prisma_client.db.litellm_usertable.find_unique( | |
where={"user_id": user_id} | |
) | |
if user_row is not None: | |
return user_row.user_email | |
return None | |
async def _get_invitation_link(self, user_id: Optional[str], base_url: str) -> str: | |
""" | |
Get invitation link for the user | |
""" | |
import asyncio | |
from litellm.proxy.proxy_server import prisma_client | |
################################################################################ | |
########## Sleep for 10 seconds to wait for the invitation link to be created ### | |
################################################################################ | |
# The UI, calls /invitation/new to generate the invitation link | |
# We wait 10 seconds to ensure the link is created | |
################################################################################ | |
await asyncio.sleep(10) | |
if prisma_client is None: | |
verbose_proxy_logger.debug( | |
f"Prisma client not found. Unable to lookup user email for user_id: {user_id}" | |
) | |
return base_url | |
if user_id is None: | |
return base_url | |
# get the latest invitation link for the user | |
invitation_rows = await prisma_client.db.litellm_invitationlink.find_many( | |
where={"user_id": user_id}, | |
order={"created_at": "desc"}, | |
) | |
if len(invitation_rows) > 0: | |
invitation_row = invitation_rows[0] | |
return self._construct_invitation_link( | |
invitation_id=invitation_row.id, base_url=base_url | |
) | |
return base_url | |
def _construct_invitation_link(self, invitation_id: str, base_url: str) -> str: | |
""" | |
Construct invitation link for the user | |
# http://localhost:4000/ui?invitation_id=7a096b3a-37c6-440f-9dd1-ba22e8043f6b | |
""" | |
return f"{base_url}/ui?invitation_id={invitation_id}" | |
async def send_email( | |
self, | |
from_email: str, | |
to_email: List[str], | |
subject: str, | |
html_body: str, | |
): | |
pass | |