DesertWolf's picture
Upload folder using huggingface_hub
447ebeb verified
"""
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