Problem
Email clients (especially Microsoft Outlook) block external images in emails sent via SendGrid, prompting recipients with a "Do you want to download external content?" warning before rendering the logo.
This happens because SendGridEmailLogger.send_email() sends HTML with an external <img src="{email_logo_url}"> URL. When the email was sent via SMTP through a corporate mail relay, Outlook trusted the sender and loaded images automatically. With SendGrid, the email originates from an external sender, so Outlook treats all linked content as untrusted.
Proposed Solution
Support embedding the logo as an inline CID attachment in the SendGrid Mail Send API payload. When a logo file path is configured, the image should be base64-encoded and attached with disposition: inline and a content_id, then the {email_logo_url} reference in the HTML body should be replaced with cid:<content_id>.
Suggested Changes
1. litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.py
In __init__:
- Read a new env var
EMAIL_LOGO_PATH (file path to the logo image)
- If set, read the file, base64-encode it, and store as a pre-built attachment dict:
{
"content": "<base64-encoded-bytes>",
"type": "image/png",
"filename": "logo.png",
"disposition": "inline",
"content_id": "logo"
}
- If the file is missing or unset, fall back to the current external URL behavior (no breaking change)
In send_email():
- If the logo attachment is available:
- Replace the
EMAIL_LOGO_URL value in html_body with cid:logo
- Add
"attachments": [<logo_attachment>] to the SendGrid JSON payload
2. No template changes needed
The existing {email_logo_url} placeholder in email templates (key_created_email.py, key_rotated_email.py, user_invitation_email.py) stays as-is. The URL-to-CID replacement happens at send time in send_email().
3. No changes to base_email.py
The EmailParams.logo_url resolution logic remains unchanged. The CID substitution is isolated to the SendGrid transport layer.
Why This Matters
- Outlook desktop (the most common corporate email client) blocks external images by default for emails from external senders
- CID inline attachments are rendered without warnings in all major email clients (Outlook, Gmail, Apple Mail, Thunderbird)
- The logo file is only ~5 KB, so the email size increase is negligible
- SMTP-based sending did not have this issue because corporate mail relays are trusted senders
Environment
- litellm version: 1.94.3
- Email provider: SendGrid (Mail Send API v3)
- Affected email client: Microsoft Outlook (desktop and OWA)
Workaround
We are currently patching sendgrid_email.py at container startup to add this behavior. Would be great to have native support so we can drop the patch.
Problem
Email clients (especially Microsoft Outlook) block external images in emails sent via SendGrid, prompting recipients with a "Do you want to download external content?" warning before rendering the logo.
This happens because
SendGridEmailLogger.send_email()sends HTML with an external<img src="{email_logo_url}">URL. When the email was sent via SMTP through a corporate mail relay, Outlook trusted the sender and loaded images automatically. With SendGrid, the email originates from an external sender, so Outlook treats all linked content as untrusted.Proposed Solution
Support embedding the logo as an inline CID attachment in the SendGrid Mail Send API payload. When a logo file path is configured, the image should be base64-encoded and attached with
disposition: inlineand acontent_id, then the{email_logo_url}reference in the HTML body should be replaced withcid:<content_id>.Suggested Changes
1.
litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.pyIn
__init__:EMAIL_LOGO_PATH(file path to the logo image){ "content": "<base64-encoded-bytes>", "type": "image/png", "filename": "logo.png", "disposition": "inline", "content_id": "logo" }In
send_email():EMAIL_LOGO_URLvalue inhtml_bodywithcid:logo"attachments": [<logo_attachment>]to the SendGrid JSON payload2. No template changes needed
The existing
{email_logo_url}placeholder in email templates (key_created_email.py,key_rotated_email.py,user_invitation_email.py) stays as-is. The URL-to-CID replacement happens at send time insend_email().3. No changes to
base_email.pyThe
EmailParams.logo_urlresolution logic remains unchanged. The CID substitution is isolated to the SendGrid transport layer.Why This Matters
Environment
Workaround
We are currently patching
sendgrid_email.pyat container startup to add this behavior. Would be great to have native support so we can drop the patch.