feat: add scheduler and mail outbox

SMTP adapter with TLS and a fake, outbox with retries, backoff, rate limit and restart safety, scheduler tick with job-run records started by gunicorn, and a send_test_mail command.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
This commit is contained in:
2026-10-18 09:18:42 +02:00
co-authored by Claude Sonnet 5.5
parent 0dc079a8ac
commit 20510a73e5
40 changed files with 1315 additions and 6 deletions
+7 -2
View File
@@ -7,6 +7,7 @@ import pytest
from portal.adapters import registry
from portal.adapters.directory import FakeDirectory
from portal.adapters.idp import FakeIdp
from portal.adapters.mail import FakeMailer
@pytest.fixture
@@ -28,14 +29,18 @@ def valid_env(tmp_path):
"OIDC_CLIENT_ID": "portal-client",
"OIDC_CLIENT_SECRET": "client-secret",
"PUBLIC_BASE_URL": "https://portal.example.test",
"SMTP_HOST": "mail.example.test",
"SMTP_USERNAME": "portal@example.test",
"SMTP_PASSWORD": "mail-password",
"MAIL_FROM": "Portal <portal@example.test>",
}
@pytest.fixture(autouse=True)
def adapters():
"""Fakes for every adapter, so no test can reach a real directory or provider."""
fakes = SimpleNamespace(directory=FakeDirectory(), idp=FakeIdp())
registry.install(fakes.directory, fakes.idp)
fakes = SimpleNamespace(directory=FakeDirectory(), idp=FakeIdp(), mailer=FakeMailer())
registry.install(fakes.directory, fakes.idp, fakes.mailer)
yield fakes
registry.install()