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
+38
View File
@@ -140,3 +140,41 @@ def test_oidc_addresses_must_be_https_origins(valid_env, name, value):
def test_a_plain_http_origin_is_allowed_only_in_debug(valid_env):
env = {**valid_env, "PUBLIC_BASE_URL": "http://localhost:8000"}
assert load_config({**env, "DEBUG": "true"}).oidc.public_base_url == "http://localhost:8000"
def test_smtp_settings_default_to_starttls_on_587_and_hide_the_password(valid_env):
smtp = load_config(valid_env).smtp
assert (smtp.host, smtp.port, smtp.security) == ("mail.example.test", 587, "starttls")
assert smtp.sender == "Portal <portal@example.test>"
assert "mail-password" not in repr(smtp)
def test_smtp_ssl_defaults_to_port_465_and_the_port_can_be_set(valid_env):
assert load_config({**valid_env, "SMTP_SECURITY": "SSL"}).smtp.port == 465
assert load_config({**valid_env, "SMTP_PORT": "2525"}).smtp.port == 2525
@pytest.mark.parametrize(
("name", "value"),
[
("SMTP_HOST", "smtp://mail.example.test"),
("SMTP_HOST", "mail.example.test:587"),
("SMTP_SECURITY", "plain"),
("SMTP_PORT", "abc"),
("SMTP_PORT", "0"),
("SMTP_PORT", "70000"),
("MAIL_FROM", "not-an-address"),
("MAIL_FROM", "a@b.test\r\nBcc: x@evil.test"),
("MAIL_FROM", "@example.test"),
],
)
def test_invalid_smtp_settings_are_refused(valid_env, name, value):
with pytest.raises(ConfigError, match=name):
load_config({**valid_env, name: value})
@pytest.mark.parametrize("name", ["SMTP_HOST", "SMTP_USERNAME", "SMTP_PASSWORD", "MAIL_FROM"])
def test_the_mail_settings_are_required(valid_env, name):
del valid_env[name]
with pytest.raises(ConfigError, match=name):
load_config(valid_env)