campaign sending prototype

This commit is contained in:
2026-06-24 16:20:44 +02:00
parent 99fee44651
commit a9d16a2c89
12 changed files with 1478 additions and 41 deletions

View File

@@ -21,18 +21,23 @@ def _redis_client() -> Redis:
return Redis.from_url(settings.redis_url, decode_responses=True)
def _distributed_rate_limit_enabled() -> bool:
return bool(getattr(settings, "celery_enabled", False))
def wait_for_rate_limit(*, key: str, messages_per_minute: int, enabled: bool = True) -> RateLimitDecision:
"""Throttle sends across worker processes using Redis when available.
The implementation stores the next allowed send timestamp per key. A Redis
lock keeps multiple Celery processes from reading/updating the timestamp at
the same time. If Redis is unavailable, it falls back to no distributed wait;
the per-container Celery concurrency still protects local development.
the same time. Direct local development runs do not have a broker, so Redis
is only used when Celery/worker mode is explicitly enabled. If Redis is
unavailable, it falls back to no distributed wait.
"""
messages_per_minute = max(1, int(messages_per_minute or 1))
gap = 60.0 / messages_per_minute
if not enabled:
if not enabled or not _distributed_rate_limit_enabled():
return RateLimitDecision(key=key, messages_per_minute=messages_per_minute, gap_seconds=gap, waited_seconds=0.0)
redis_key = f"multimailer:ratelimit:{key}:next_allowed"