diff --git a/backend/app/services/system_email_service.py b/backend/app/services/system_email_service.py index a0f3573d..445beac2 100644 --- a/backend/app/services/system_email_service.py +++ b/backend/app/services/system_email_service.py @@ -156,4 +156,9 @@ def run_background_email_job(job, *args, **kwargs) -> None: """Bridge Starlette background tasks to async email jobs.""" result = job(*args, **kwargs) if inspect.isawaitable(result): - fire_and_forget(result) + try: + asyncio.get_running_loop() + except RuntimeError: + asyncio.run(result) + else: + fire_and_forget(result) diff --git a/backend/tests/test_password_reset_and_notifications.py b/backend/tests/test_password_reset_and_notifications.py index 56998c79..6806572a 100644 --- a/backend/tests/test_password_reset_and_notifications.py +++ b/backend/tests/test_password_reset_and_notifications.py @@ -320,6 +320,17 @@ def sendmail(self, from_address: str, to_addresses: list[str], message: str): assert captured["to"] == ["alice@example.com"] +def test_run_background_email_job_executes_awaitable_without_running_loop(): + captured = {"value": None} + + async def fake_job(value: str): + captured["value"] = value + + system_email_service.run_background_email_job(fake_job, "sent") + + assert captured["value"] == "sent" + + @pytest.mark.asyncio async def test_reset_password_updates_user(monkeypatch): user = make_user(password_hash=auth_api.hash_password("old-password")) diff --git a/docker-compose.yml b/docker-compose.yml index 05320f3b..494fb768 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,8 @@ services: CLAWITH_PIP_TRUSTED_HOST: ${CLAWITH_PIP_TRUSTED_HOST:-} restart: unless-stopped command: ["/bin/bash", "/app/entrypoint.sh"] + env_file: + - ./.env environment: DATABASE_URL: postgresql+asyncpg://clawith:clawith@postgres:5432/clawith REDIS_URL: redis://redis:6379/0