Spaces:
Configuration error
Configuration error
File size: 7,696 Bytes
447ebeb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
"""
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
|