Skip to content

fix(proxy): run SMTP send_email off the event loop with a connection timeout - #38473

Merged
yassin-berriai merged 10 commits into
litellm_internal_stagingfrom
litellm_smtp_send_email_timeout
Aug 29, 2026
Merged

yassin-berriai merged 10 commits into
litellm_internal_stagingfrom
litellm_smtp_send_email_timeout

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Sending an email could freeze the whole proxy
  • SMTP ran synchronously on the event loop with no timeout
  • An unreachable SMTP host stalled /health/liveliness, so k8s killed the pod

How it solves it:

  • SMTP connections now get a timeout (SMTP_TIMEOUT env var, default 30s)
  • The whole SMTP session runs in a worker thread via asyncio.to_thread
  • Also stops carrying forward the dead circleci codecov flag, whose stale line maps failed codecov/patch here

User Flow

Before: an operator whose SMTP server hangs sees the whole proxy stop responding and the pod get killed

  1. The proxy sends any email (key created invite, budget alert, or GET https://litellm-domain/health/services?service=email) while the SMTP host accepts TCP but never answers
  2. Every request to the proxy hangs, including GET https://litellm-domain/health/liveliness which times out instead of returning "I'm alive!"
  3. Kubernetes liveness probes fail and the pod is killed with exit 137 and no traceback

After: the same hung SMTP server only affects the email itself

  1. The proxy sends the same email while the SMTP host accepts TCP but never answers
  2. GET https://litellm-domain/health/liveliness keeps returning 200 "I'm alive!" the whole time
  3. After 30 seconds (or SMTP_TIMEOUT) the email attempt gives up and the error is logged; the pod stays up

Relevant issues

Linear ticket

Resolves LIT-4992

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Setup shared by both runs: a local banner-less SMTP server (accepts TCP on 127.0.0.1:2525, never sends the SMTP greeting), and the proxy started with SMTP_HOST=127.0.0.1 SMTP_PORT=2525 SMTP_TLS=False [email protected] [email protected] on port 4000. The email is triggered through /health/services?service=email, which goes through the key-created email path and ends in the shared SMTP send helper, the same helper every email path (invites, budget alerts, enterprise SMTP logger) ends in

Before (807ee7f)

  1. curl -s -m 5 http://localhost:4000/health/liveliness returns "I'm alive!"
  2. curl -s -m 5 "http://localhost:4000/health/services?service=email" -H "Authorization: Bearer sk-1234" & starts the test email; the SMTP connect blocks the event loop
  3. curl -s -m 5 http://localhost:4000/health/liveliness while the email is in flight: no response, curl exits 28 (timed out). This is the state where the kubelet kills the pod

After (75fdd7f)

  1. curl -s -m 5 http://localhost:4000/health/liveliness returns "I'm alive!"
  2. curl -s -m 65 "http://localhost:4000/health/services?service=email" -H "Authorization: Bearer sk-1234" & starts the same test email against the same hung SMTP server
  3. curl -s -m 5 http://localhost:4000/health/liveliness 2s into the hung SMTP send: "I'm alive!", curl exit 0
  4. curl -s -m 5 http://localhost:4000/health/liveliness 12s in: "I'm alive!", curl exit 0
  5. The email endpoint returns {"status":"success","message":"Mock Email Alert sent, verify Email Alert Received"} and 30s after the send started the proxy logs An error occurred while sending the email:Connection unexpectedly closed: timed out (TimeoutError: timed out in the traceback), matching the default SMTP_TIMEOUT=30

Type

🐛 Bug Fix

Caveats (if any)

Low

  • Failed sends now surface after SMTP_TIMEOUT instead of hanging forever

Link to Devin session: https://app.devin.ai/sessions/d28d0fbec50b4b709720d86bca5bc416
Open in Devin Desktop: https://app.devin.ai/desktop/session/d28d0fbec50b4b709720d86bca5bc416?variant=devin
Requested by: @yassin-berriai

…timeout

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

PR #38473 has no labels (no enterprise), so it's out of scope — no GitHub or Linear changes made.

@greptile-apps

greptile-apps Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves blocking SMTP sessions off the event loop, applies a configurable connection timeout, and contains malformed timeout configuration within the email error boundary.

  • Adds SMTP_TIMEOUT, defaulting to 30 seconds, to SMTP and SMTP-over-SSL connections.
  • Runs the complete SMTP session in a worker thread so proxy liveness remains responsive.
  • Adds tests for timeout forwarding, environment overrides, malformed values, and worker-thread execution.
  • Disables carryforward for the stale CircleCI Codecov flag.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/utils.py Moves synchronous SMTP work to a worker thread, forwards a connection timeout, and now catches malformed timeout configuration.
tests/test_litellm/proxy/utils/prisma_and_spend/test_send_email.py Adds focused regression coverage for timeout configuration, malformed values, and off-event-loop execution.
tests/test_litellm/proxy/utils/prisma_and_spend/conftest.py Extends the in-memory SMTP fixture to capture connection arguments and worker-thread identity.
tests/test_litellm/proxy/test_proxy_utils.py Updates connection-factory tests to verify timeout forwarding for plain SMTP and SMTP over SSL.
codecov.yaml Prevents stale CircleCI coverage from carrying forward between reports.

Reviews (5): Last reviewed commit: "ci: stop carrying forward the dead circl..." | Re-trigger Greptile

Comment thread litellm/proxy/utils.py Outdated
@codecov

codecov Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…for timeout

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Good catch, moved the float() inside the try in 75fdd7f and added a malformed SMTP_TIMEOUT regression test

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

@codspeed

codspeed Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_smtp_send_email_timeout (0134666) with litellm_internal_staging (c62c2af)

Open in CodSpeed

@yassin-berriai
yassin-berriai enabled auto-merge (squash) August 29, 2026 20:04
auto-merge was automatically disabled August 29, 2026 20:05

Pull request was closed

yassin-berriai and others added 7 commits August 29, 2026 20:20
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai re-review: since the last reviewed commit the branch merged litellm_internal_staging and added codecov.yml (disable carryforward/join for the stale circleci flag); no product code changed.

@yassin-berriai
yassin-berriai merged commit 3e2999f into litellm_internal_staging Aug 29, 2026
80 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_smtp_send_email_timeout branch August 29, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants