diff --git a/litellm/integrations/slack_alerting.py b/litellm/integrations/slack_alerting.py
index 608e0113cf0..620320f6749 100644
--- a/litellm/integrations/slack_alerting.py
+++ b/litellm/integrations/slack_alerting.py
@@ -14,6 +14,7 @@ from pydantic import BaseModel
from enum import Enum
from datetime import datetime as dt, timedelta, timezone
from litellm.integrations.custom_logger import CustomLogger
+from litellm.proxy._types import WebhookEvent
import random
from typing import TypedDict
from openai import APIError
@@ -30,6 +31,13 @@ class OutageModel(TypedDict):
major_alert_sent: bool
last_updated_at: float
+# we use this for the email header, please send a test email if you change this. verify it looks good on email
+LITELLM_LOGO_URL = "https://litellm-listing.s3.amazonaws.com/litellm_logo.png"
+EMAIL_LOGO_URL = os.getenv(
+ "SMTP_SENDER_LOGO", "https://litellm-listing.s3.amazonaws.com/litellm_logo.png"
+)
+EMAIL_SUPPORT_CONTACT = os.getenv("EMAIL_SUPPORT_CONTACT", "support@berri.ai")
+
class LiteLLMBase(BaseModel):
"""
@@ -57,12 +65,6 @@ class SlackAlertingArgs(LiteLLMBase):
max_outage_alert_list_size: int = 10 # prevent memory leak
-class WebhookEvent(CallInfo):
- event: Literal["budget_crossed", "threshold_crossed", "projected_limit_exceeded"]
- event_group: Literal["user", "key", "team", "proxy"]
- event_message: str # human-readable description of event
-
-
class DeploymentMetrics(LiteLLMBase):
"""
Metrics per deployment, stored in cache
@@ -664,6 +666,9 @@ class SlackAlerting(CustomLogger):
_id = user_info.token
# percent of max_budget left to spend
+ if user_info.max_budget is None:
+ return
+
if user_info.max_budget > 0:
percent_left = (
user_info.max_budget - user_info.spend
@@ -946,6 +951,103 @@ Model Info:
return False
+ async def send_key_created_email(self, webhook_event: WebhookEvent) -> bool:
+ from litellm.proxy.utils import send_email
+
+ if self.alerting is None or "email" not in self.alerting:
+ # do nothing if user does not want email alerts
+ return False
+
+ # make sure this is a premium user
+ from litellm.proxy.proxy_server import premium_user
+ from litellm.proxy.proxy_server import CommonProxyErrors, prisma_client
+
+ if premium_user != True:
+ raise Exception(
+ f"Trying to use Email Alerting on key creation\n {CommonProxyErrors.not_premium_user.value}"
+ )
+
+ event_name = webhook_event.event_message
+ recipient_email = webhook_event.user_email
+ recipient_user_id = webhook_event.user_id
+ if (
+ recipient_email is None
+ and recipient_user_id is not None
+ and prisma_client is not None
+ ):
+ user_row = await prisma_client.db.litellm_usertable.find_unique(
+ where={"user_id": recipient_user_id}
+ )
+
+ if user_row is not None:
+ recipient_email = user_row.user_email
+
+ key_name = webhook_event.key_alias
+ key_token = webhook_event.token
+ key_budget = webhook_event.max_budget
+
+ email_html_content = "Alert from LiteLLM Server"
+ if recipient_email is None:
+ verbose_proxy_logger.error(
+ "Trying to send email alert to no recipient", extra=webhook_event.dict()
+ )
+ email_html_content = f"""
+
+
+
Hi {recipient_email},
+
+ I'm happy to provide you with an OpenAI Proxy API Key, loaded with ${key_budget} per month.
+
+
+ Key: {key_token}
+
+
+
+
+ import openai
+ client = openai.OpenAI(
+ api_key="{key_token}",
+ base_url={os.getenv("PROXY_BASE_URL", "http://0.0.0.0:4000")}
+ )
+
+ response = client.chat.completions.create(
+ model="gpt-3.5-turbo", # model to send to the proxy
+ messages = [
+ {{
+ "role": "user",
+ "content": "this is a test request, write a short poem"
+ }}
+ ]
+ )
+
+
+
+
+ If you have any questions, please send an email to {EMAIL_SUPPORT_CONTACT} Hi {user_name},
- Your LLM API usage this month has reached your account's monthly budget of ${max_budget}
+ Your LLM API usage this month has reached your account's monthly budget of ${max_budget}
API requests will be rejected until either (a) you increase your monthly budget or (b) your monthly usage resets at the beginning of the next calendar month.
- If you have any questions, please send an email to support@berri.ai
+ If you have any questions, please send an email to {EMAIL_SUPPORT_CONTACT}
Best,
The LiteLLM team
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index b2c38794314..e8b3e657205 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -1096,7 +1096,7 @@ class CallInfo(LiteLLMBase):
"""Used for slack budget alerting"""
spend: float
- max_budget: float
+ max_budget: Optional[float] = None
token: str = Field(description="Hashed value of that key")
user_id: Optional[str] = None
team_id: Optional[str] = None
@@ -1104,3 +1104,11 @@ class CallInfo(LiteLLMBase):
key_alias: Optional[str] = None
projected_exceeded_date: Optional[str] = None
projected_spend: Optional[float] = None
+
+
+class WebhookEvent(CallInfo):
+ event: Literal[
+ "budget_crossed", "threshold_crossed", "projected_limit_exceeded", "key_created"
+ ]
+ event_group: Literal["user", "key", "team", "proxy"]
+ event_message: str # human-readable description of event
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index fb7b65072e7..d558107c3f4 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -5402,6 +5402,25 @@ async def generate_key_fn(
response["soft_budget"] = (
data.soft_budget
) # include the user-input soft budget in the response
+ event = WebhookEvent(
+ event="key_created",
+ event_group="key",
+ event_message=f"API Key Created",
+ token=response.get("token", ""),
+ spend=response.get("spend", 0.0),
+ max_budget=response.get("max_budget", 0.0),
+ user_id=response.get("user_id", None),
+ team_id=response.get("team_id", "Default Team"),
+ key_alias=response.get("key_alias", None),
+ )
+
+ # If user configured email alerting - send an Email letting their end-user know the key was created
+ asyncio.create_task(
+ proxy_logging_obj.slack_alerting_instance.send_key_created_email(
+ webhook_event=event,
+ )
+ )
+
return GenerateKeyResponse(**response)
except Exception as e:
traceback.print_exc()
diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py
index fd2001b04e5..b710165cb44 100644
--- a/litellm/proxy/utils.py
+++ b/litellm/proxy/utils.py
@@ -12,6 +12,7 @@ from litellm.proxy._types import (
LiteLLM_TeamTable,
Member,
CallInfo,
+ WebhookEvent,
AlertType,
)
from litellm.caching import DualCache, RedisCache