Skip to content

Commit 923004d

Browse files
authored
Merge pull request #12218 from BerriAI/email_template
Customizable Email template - Subject and Signature
2 parents 1513cb5 + e53eb6e commit 923004d

File tree

5 files changed

+280
-33
lines changed

5 files changed

+280
-33
lines changed

docs/my-website/docs/proxy/config_settings.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,9 @@ router_settings:
434434
| DOCS_URL | The path to the Swagger API documentation. **By default this is "/"**
435435
| EMAIL_LOGO_URL | URL for the logo used in emails
436436
| EMAIL_SUPPORT_CONTACT | Support contact email address
437+
| EMAIL_SIGNATURE | Custom HTML footer/signature for all emails. Can include HTML tags for formatting and links.
438+
| EMAIL_SUBJECT_INVITATION | Custom subject template for invitation emails.
439+
| EMAIL_SUBJECT_KEY_CREATED | Custom subject template for key creation emails.
437440
| EXPERIMENTAL_MULTI_INSTANCE_RATE_LIMITING | Flag to enable new multi-instance rate limiting. **Default is False**
438441
| FIREWORKS_AI_4_B | Size parameter for Fireworks AI 4B model. Default is 4
439442
| FIREWORKS_AI_16_B | Size parameter for Fireworks AI 16B model. Default is 16

docs/my-website/docs/proxy/email.md

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,104 @@ On the Create Key Modal, Select Advanced Settings > Set Send Email to True.
124124
/>
125125
126126
127-
128-
129-
## Customizing Email Branding
127+
## Email Customization
130128
131129
:::info
132130
133131
Customizing Email Branding is an Enterprise Feature [Get in touch with us for a Free Trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
134132
135133
:::
136134
137-
LiteLLM allows you to customize the:
138-
- Logo on the Email
139-
- Email support contact
135+
LiteLLM allows you to customize various aspects of your email notifications. Below is a complete reference of all customizable fields:
136+
137+
| Field | Environment Variable | Type | Default Value | Example | Description |
138+
|-------|-------------------|------|---------------|---------|-------------|
139+
| Logo URL | `EMAIL_LOGO_URL` | string | LiteLLM logo | `"https://your-company.com/logo.png"` | Public URL to your company logo |
140+
| Support Contact | `EMAIL_SUPPORT_CONTACT` | string | [email protected] | `"[email protected]"` | Email address for user support |
141+
| Email Signature | `EMAIL_SIGNATURE` | string (HTML) | Standard LiteLLM footer | `"<p>Best regards,<br/>Your Team</p><p><a href='https://your-company.com'>Visit us</a></p>"` | HTML-formatted footer for all emails |
142+
| Invitation Subject | `EMAIL_SUBJECT_INVITATION` | string | "LiteLLM: New User Invitation" | `"Welcome to Your Company!"` | Subject line for invitation emails |
143+
| Key Creation Subject | `EMAIL_SUBJECT_KEY_CREATED` | string | "LiteLLM: API Key Created" | `"Your New API Key is Ready"` | Subject line for key creation emails |
144+
145+
146+
## HTML Support in Email Signature
147+
148+
The `EMAIL_SIGNATURE` field supports HTML formatting for rich, branded email footers. Here's an example of what you can include:
149+
150+
```html
151+
<p>Best regards,<br/>The LiteLLM Team</p>
152+
<p>
153+
<a href='https://docs.litellm.ai'>Documentation</a> |
154+
<a href='https://github.com/BerriAI/litellm'>GitHub</a>
155+
</p>
156+
<p style='font-size: 12px; color: #666;'>
157+
This is an automated message from LiteLLM Proxy
158+
</p>
159+
```
160+
161+
Supported HTML features:
162+
- Text formatting (bold, italic, etc.)
163+
- Line breaks (`<br/>`)
164+
- Links (`<a href='...'>`)
165+
- Paragraphs (`<p>`)
166+
- Basic inline styling
167+
- Company information and social media links
168+
- Legal disclaimers or terms of service links
169+
170+
## Environment Variables
171+
172+
You can customize the following aspects of emails through environment variables:
173+
174+
```bash
175+
# Email Branding
176+
EMAIL_LOGO_URL="https://your-company.com/logo.png" # Custom logo URL
177+
EMAIL_SUPPORT_CONTACT="[email protected]" # Support contact email
178+
EMAIL_SIGNATURE="<p>Best regards,<br/>Your Company Team</p><p><a href='https://your-company.com'>Visit our website</a></p>" # Custom HTML footer/signature
140179
141-
Set the following in your env to customize your emails
180+
# Email Subject Lines
181+
EMAIL_SUBJECT_INVITATION="Welcome to Your Company!" # Subject for invitation emails
182+
EMAIL_SUBJECT_KEY_CREATED="Your API Key is Ready" # Subject for key creation emails
183+
```
184+
185+
## HTML Support in Email Signature
186+
187+
The `EMAIL_SIGNATURE` environment variable supports HTML formatting, allowing you to create rich, branded email footers. You can include:
188+
189+
- Text formatting (bold, italic, etc.)
190+
- Line breaks using `<br/>`
191+
- Links using `<a href='...'>`
192+
- Paragraphs using `<p>`
193+
- Company information and social media links
194+
- Legal disclaimers or terms of service links
195+
196+
Example HTML signature:
197+
```html
198+
<p>Best regards,<br/>The LiteLLM Team</p>
199+
<p>
200+
<a href='https://docs.litellm.ai'>Documentation</a> |
201+
<a href='https://github.com/BerriAI/litellm'>GitHub</a>
202+
</p>
203+
<p style='font-size: 12px; color: #666;'>
204+
This is an automated message from LiteLLM Proxy
205+
</p>
206+
```
207+
208+
## Default Templates
209+
210+
If environment variables are not set, LiteLLM will use default templates:
211+
212+
- Default logo: LiteLLM logo
213+
- Default support contact: [email protected]
214+
- Default signature: Standard LiteLLM footer
215+
- Default subjects: "LiteLLM: \{event_message\}" (replaced with actual event message)
142216

143-
```shell
144-
EMAIL_LOGO_URL="https://litellm-listing.s3.amazonaws.com/litellm_logo.png" # public url to your logo
145-
EMAIL_SUPPORT_CONTACT="[email protected]" # Your company support email
217+
## Template Variables
218+
219+
When setting custom email subjects, you can use template variables that will be replaced with actual values:
220+
221+
```bash
222+
# Examples of template variable usage
223+
EMAIL_SUBJECT_INVITATION="Welcome to \{company_name\}!"
224+
EMAIL_SUBJECT_KEY_CREATED="Your \{company_name\} API Key"
146225
```
226+
227+
The system will automatically replace `\{event_message\}` and other template variables with their actual values when sending emails.

enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
class BaseEmailLogger(CustomLogger):
3030
DEFAULT_LITELLM_EMAIL = "[email protected]"
3131
DEFAULT_SUPPORT_EMAIL = "[email protected]"
32+
DEFAULT_SUBJECT_TEMPLATES = {
33+
EmailEvent.new_user_invitation: "LiteLLM: {event_message}",
34+
EmailEvent.virtual_key_created: "LiteLLM: {event_message}",
35+
}
3236

3337
async def send_user_invitation_email(self, event: WebhookEvent):
3438
"""
@@ -38,8 +42,8 @@ async def send_user_invitation_email(self, event: WebhookEvent):
3842
email_event=EmailEvent.new_user_invitation,
3943
user_id=event.user_id,
4044
user_email=getattr(event, "user_email", None),
45+
event_message=event.event_message,
4146
)
42-
# Implement invitation email logic using email_params
4347

4448
verbose_proxy_logger.debug(
4549
f"send_user_invitation_email_event: {json.dumps(event, indent=4, default=str)}"
@@ -50,13 +54,13 @@ async def send_user_invitation_email(self, event: WebhookEvent):
5054
recipient_email=email_params.recipient_email,
5155
base_url=email_params.base_url,
5256
email_support_contact=email_params.support_contact,
53-
email_footer=EMAIL_FOOTER,
57+
email_footer=email_params.signature,
5458
)
5559

5660
await self.send_email(
5761
from_email=self.DEFAULT_LITELLM_EMAIL,
5862
to_email=[email_params.recipient_email],
59-
subject=f"LiteLLM: {event.event_message}",
63+
subject=email_params.subject,
6064
html_body=email_html_content,
6165
)
6266

@@ -68,11 +72,11 @@ async def send_key_created_email(
6872
"""
6973
Send email to user after creating key for the user
7074
"""
71-
7275
email_params = await self._get_email_params(
7376
user_id=send_key_created_email_event.user_id,
7477
user_email=send_key_created_email_event.user_email,
7578
email_event=EmailEvent.virtual_key_created,
79+
event_message=send_key_created_email_event.event_message,
7680
)
7781

7882
verbose_proxy_logger.debug(
@@ -86,13 +90,13 @@ async def send_key_created_email(
8690
key_token=send_key_created_email_event.virtual_key,
8791
base_url=email_params.base_url,
8892
email_support_contact=email_params.support_contact,
89-
email_footer=EMAIL_FOOTER,
93+
email_footer=email_params.signature,
9094
)
9195

9296
await self.send_email(
9397
from_email=self.DEFAULT_LITELLM_EMAIL,
9498
to_email=[email_params.recipient_email],
95-
subject=f"LiteLLM: {send_key_created_email_event.event_message}",
99+
subject=email_params.subject,
96100
html_body=email_html_content,
97101
)
98102
pass
@@ -102,16 +106,57 @@ async def _get_email_params(
102106
email_event: EmailEvent,
103107
user_id: Optional[str] = None,
104108
user_email: Optional[str] = None,
109+
event_message: Optional[str] = None,
105110
) -> EmailParams:
106111
"""
107112
Get common email parameters used across different email sending methods
108113
109114
Returns:
110-
EmailParams object containing logo_url, support_contact, base_url, and recipient_email
115+
EmailParams object containing logo_url, support_contact, base_url, recipient_email, subject, and signature
111116
"""
112-
logo_url = os.getenv("EMAIL_LOGO_URL", None) or LITELLM_LOGO_URL
113-
support_contact = os.getenv("EMAIL_SUPPORT_CONTACT", self.DEFAULT_SUPPORT_EMAIL)
114-
base_url = os.getenv("PROXY_BASE_URL", "http://0.0.0.0:4000")
117+
# Get email parameters with premium check for custom values
118+
custom_logo = os.getenv("EMAIL_LOGO_URL", None)
119+
custom_support = os.getenv("EMAIL_SUPPORT_CONTACT", None)
120+
custom_signature = os.getenv("EMAIL_SIGNATURE", None)
121+
custom_subject_invitation = os.getenv("EMAIL_SUBJECT_INVITATION", None)
122+
custom_subject_key_created = os.getenv("EMAIL_SUBJECT_KEY_CREATED", None)
123+
124+
# Track which custom values were not applied
125+
unused_custom_fields = []
126+
127+
# Function to safely get custom value or default
128+
def get_custom_or_default(custom_value: Optional[str], default_value: str, field_name: str) -> str:
129+
if custom_value is not None: # Only check premium if trying to use custom value
130+
from litellm.proxy.proxy_server import premium_user
131+
if premium_user is not True:
132+
unused_custom_fields.append(field_name)
133+
return default_value
134+
return custom_value
135+
return default_value
136+
137+
# Get parameters, falling back to defaults if custom values aren't allowed
138+
logo_url = get_custom_or_default(custom_logo, LITELLM_LOGO_URL, "logo URL")
139+
support_contact = get_custom_or_default(custom_support, self.DEFAULT_SUPPORT_EMAIL, "support contact")
140+
base_url = os.getenv("PROXY_BASE_URL", "http://0.0.0.0:4000") # Not a premium feature
141+
signature = get_custom_or_default(custom_signature, EMAIL_FOOTER, "email signature")
142+
143+
# Get custom subject template based on email event type
144+
if email_event == EmailEvent.new_user_invitation:
145+
subject_template = get_custom_or_default(
146+
custom_subject_invitation,
147+
self.DEFAULT_SUBJECT_TEMPLATES[EmailEvent.new_user_invitation],
148+
"invitation subject template"
149+
)
150+
elif email_event == EmailEvent.virtual_key_created:
151+
subject_template = get_custom_or_default(
152+
custom_subject_key_created,
153+
self.DEFAULT_SUBJECT_TEMPLATES[EmailEvent.virtual_key_created],
154+
"key created subject template"
155+
)
156+
else:
157+
subject_template = "LiteLLM: {event_message}"
158+
159+
subject = subject_template.format(event_message=event_message) if event_message else "LiteLLM Notification"
115160

116161
recipient_email: Optional[
117162
str
@@ -127,11 +172,25 @@ async def _get_email_params(
127172
user_id=user_id, base_url=base_url
128173
)
129174

175+
# If any custom fields were not applied, log a warning
176+
if unused_custom_fields:
177+
fields_str = ", ".join(unused_custom_fields)
178+
warning_msg = (
179+
f"Email sent with default values instead of custom values for: {fields_str}. "
180+
"This is an Enterprise feature. To use custom email fields, please upgrade to LiteLLM Enterprise. "
181+
"Schedule a meeting here: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat"
182+
)
183+
verbose_proxy_logger.warning(
184+
f"{warning_msg}"
185+
)
186+
130187
return EmailParams(
131188
logo_url=logo_url,
132189
support_contact=support_contact,
133190
base_url=base_url,
134191
recipient_email=recipient_email,
192+
subject=subject,
193+
signature=signature,
135194
)
136195

137196
def _format_key_budget(self, max_budget: Optional[float]) -> str:

enterprise/litellm_enterprise/types/enterprise_callbacks/send_emails.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55

66
from litellm.proxy._types import WebhookEvent
77

8-
98
class EmailParams(BaseModel):
109
logo_url: str
1110
support_contact: str
1211
base_url: str
1312
recipient_email: str
13+
subject: str
14+
signature: str
1415

1516

1617
class SendKeyCreatedEmailEvent(WebhookEvent):
1718
virtual_key: str
1819
"""
1920
The virtual key that was created
20-
2121
this will be sk-123xxx, since we will be emailing this to the user to start using the key
2222
"""
2323

@@ -26,35 +26,25 @@ class EmailEvent(str, enum.Enum):
2626
virtual_key_created = "Virtual Key Created"
2727
new_user_invitation = "New User Invitation"
2828

29-
3029
class EmailEventSettings(BaseModel):
3130
event: EmailEvent
3231
enabled: bool
33-
34-
3532
class EmailEventSettingsUpdateRequest(BaseModel):
3633
settings: List[EmailEventSettings]
37-
38-
3934
class EmailEventSettingsResponse(BaseModel):
4035
settings: List[EmailEventSettings]
41-
42-
4336
class DefaultEmailSettings(BaseModel):
4437
"""Default settings for email events"""
45-
4638
settings: Dict[EmailEvent, bool] = Field(
4739
default_factory=lambda: {
4840
EmailEvent.virtual_key_created: False, # Off by default
4941
EmailEvent.new_user_invitation: True, # On by default
5042
}
5143
)
52-
5344
def to_dict(self) -> Dict[str, bool]:
5445
"""Convert to dictionary with string keys for storage"""
5546
return {event.value: enabled for event, enabled in self.settings.items()}
56-
5747
@classmethod
5848
def get_defaults(cls) -> Dict[str, bool]:
5949
"""Get the default settings as a dictionary with string keys"""
60-
return cls().to_dict()
50+
return cls().to_dict()

0 commit comments

Comments
 (0)