mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
feat(sendgrid): support an optional SENDGRID_REPLY_TO_EMAIL on outbound emails
This commit is contained in:
parent
c8635ecc67
commit
8fc119058e
2 changed files with 58 additions and 1 deletions
|
|
@ -5,7 +5,7 @@ Docs: https://docs.sendgrid.com/api-reference/mail-send/mail-send
|
|||
"""
|
||||
|
||||
import os
|
||||
from typing import List
|
||||
from typing import Final, List
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
|
|
@ -25,6 +25,12 @@ class SendGridEmailLogger(BaseEmailLogger):
|
|||
|
||||
Required env vars:
|
||||
- SENDGRID_API_KEY
|
||||
|
||||
Optional env vars:
|
||||
- SENDGRID_SENDER_EMAIL: Override the sender address. When unset, falls back to
|
||||
the `from_email` argument passed by the caller.
|
||||
- SENDGRID_REPLY_TO_EMAIL: Address recipients reply to. When unset, SendGrid
|
||||
defaults Reply-To to the sender address.
|
||||
"""
|
||||
|
||||
def __init__(self, internal_usage_cache=None, **kwargs):
|
||||
|
|
@ -34,6 +40,7 @@ class SendGridEmailLogger(BaseEmailLogger):
|
|||
)
|
||||
self.sendgrid_api_key = os.getenv("SENDGRID_API_KEY")
|
||||
self.sendgrid_sender_email = os.getenv("SENDGRID_SENDER_EMAIL")
|
||||
self.sendgrid_reply_to_email = os.getenv("SENDGRID_REPLY_TO_EMAIL")
|
||||
verbose_logger.debug("SendGrid Email Logger initialized.")
|
||||
|
||||
async def send_email(
|
||||
|
|
@ -54,8 +61,15 @@ class SendGridEmailLogger(BaseEmailLogger):
|
|||
f"Sending email via SendGrid from {sender_email} to {to_email} with subject {subject}"
|
||||
)
|
||||
|
||||
reply_to: Final = (
|
||||
{"reply_to": {"email": self.sendgrid_reply_to_email}}
|
||||
if self.sendgrid_reply_to_email
|
||||
else {}
|
||||
)
|
||||
|
||||
payload = {
|
||||
"from": {"email": sender_email},
|
||||
**reply_to,
|
||||
"personalizations": [
|
||||
{
|
||||
"to": [{"email": email} for email in to_email],
|
||||
|
|
|
|||
|
|
@ -133,3 +133,46 @@ async def test_send_email_multiple_recipients(mock_env_vars, mock_async_client):
|
|||
{"email": "recipient1@example.com"},
|
||||
{"email": "recipient2@example.com"},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_email_sets_reply_to_when_configured(monkeypatch, mock_async_client):
|
||||
"""SENDGRID_REPLY_TO_EMAIL is sent as the payload's reply_to and leaves from alone."""
|
||||
monkeypatch.setenv("SENDGRID_API_KEY", "test_api_key")
|
||||
monkeypatch.delenv("SENDGRID_SENDER_EMAIL", raising=False)
|
||||
monkeypatch.setenv("SENDGRID_REPLY_TO_EMAIL", "litellm-alerts@example.com")
|
||||
|
||||
logger = SendGridEmailLogger()
|
||||
logger.async_httpx_client = mock_async_client
|
||||
|
||||
await logger.send_email(
|
||||
from_email="no-reply@example.com",
|
||||
to_email=["recipient@example.com"],
|
||||
subject="Test Subject",
|
||||
html_body="<p>Test email body</p>",
|
||||
)
|
||||
|
||||
payload = mock_async_client.post.call_args[1]["json"]
|
||||
assert payload["reply_to"] == {"email": "litellm-alerts@example.com"}
|
||||
assert payload["from"] == {"email": "no-reply@example.com"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_email_omits_reply_to_when_not_configured(monkeypatch, mock_async_client):
|
||||
"""With SENDGRID_REPLY_TO_EMAIL unset the key is absent, not null, so SendGrid
|
||||
keeps defaulting Reply-To to the sender."""
|
||||
monkeypatch.setenv("SENDGRID_API_KEY", "test_api_key")
|
||||
monkeypatch.delenv("SENDGRID_REPLY_TO_EMAIL", raising=False)
|
||||
|
||||
logger = SendGridEmailLogger()
|
||||
logger.async_httpx_client = mock_async_client
|
||||
|
||||
await logger.send_email(
|
||||
from_email="no-reply@example.com",
|
||||
to_email=["recipient@example.com"],
|
||||
subject="Test Subject",
|
||||
html_body="<p>Test email body</p>",
|
||||
)
|
||||
|
||||
payload = mock_async_client.post.call_args[1]["json"]
|
||||
assert "reply_to" not in payload
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue