Skip to content

bug: SMTP email sends block the backend's event loop (no timeout) #3637

Closed

Description

@webmixgamer

Summary

send_email in src/backend/services/email_service.py is async, but its SMTP branch calls the synchronous _send_smtp, which opens smtplib.SMTP(SMTP_HOST, SMTP_PORT) with no timeout. The whole send therefore runs on the backend worker's event loop. While it connects, authenticates and sends, every other request on that worker waits. A slow or unreachable SMTP server stalls the worker until the OS gives up on the socket. Login codes, invites and notifications all take this path when the email provider is smtp.

Context

Found in the review of #3634 (abilityai/trinity-enterprise#837 PR 2). Its new account endpoint bounds the invite email with asyncio.wait_for(..., 10).

  • That bound works for the HTTP providers (Resend, SendGrid), whose sends are real awaits.
  • It cannot fire for SMTP: a blocking call inside a coroutine never yields to the loop.
  • Installs on the console, resend or sendgrid provider are unaffected.

Acceptance Criteria

  • The SMTP send runs off the event loop (asyncio.to_thread), so the worker keeps serving other requests while it runs.
  • smtplib.SMTP(...) gets an explicit socket timeout, as a documented setting or constant.
  • A test blocks the way SMTP does (a thread-blocking fake, not asyncio.sleep). It shows the loop stays responsive and that a caller's asyncio.wait_for bound fires.
  • Callers that use the send's return value (login codes) still get it.

Technical Notes

  • src/backend/services/email_service.py (send_email, _send_smtp).
  • Background: docs/memory/learnings/2026-10-12-wait-for-cannot-bound-a-sync-call-inside-a-coroutine.md.

Activity

  1. added
    complexity-lowComplexity: low (board points 1-3)
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 12, 2026
  2. added and removed
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 19, 2026
  3. self-assigned this
    on Oct 19, 2026
  4. obasilakis commented on Oct 19, 2026

    @obasilakis
    Contributor

    /claim

  5. github-actions commented on Oct 19, 2026

    @github-actions

    @obasilakis This issue is already assigned to obasilakis. Use /unclaim first if you want to take over.

  6. added
    status-in-devMerged to dev, awaiting release cut to main
    and removed on Oct 19, 2026
  7. removed
    status-in-devMerged to dev, awaiting release cut to main
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions