Files
sms-api-wrapper/sms/guard.py
chelsea db5bde0a70 first commit
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 04:39:53 +00:00

74 lines
2.1 KiB
Python

"""Outbound send guard: per-UTC-day counter + structured logging.
The voip.ms API caps sending at 100 SMS/MMS per day. This guard refuses
sends beyond a configurable limit (default 100) before they hit the network,
and logs every outbound send.
Note: this counter is per-process. If you run multiple instances behind a
load balancer, replace it with a shared store (Redis, etc.).
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from .exceptions import VoipMsRateLimitError
logger = logging.getLogger("voipms.sms.guard")
class DailySendCounter:
"""Counts outbound sends per UTC day, refusing sends past the limit."""
def __init__(self, limit: int) -> None:
self._limit = limit
self._date: str | None = None
self._count = 0
@staticmethod
def _today() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
@property
def used_today(self) -> int:
if self._date != self._today():
return 0
return self._count
@property
def limit(self) -> int:
return self._limit
def check_and_increment(self) -> int:
"""Raise VoipMsRateLimitError if the daily cap is hit; otherwise increment.
Returns the count after this send.
"""
today = self._today()
if self._date != today:
self._date = today
self._count = 0
if self._count >= self._limit:
raise VoipMsRateLimitError(
f"daily send limit ({self._limit}) reached",
limit=self._limit,
used_today=self._count,
)
self._count += 1
return self._count
def log_send(self, *, kind: str, did: str, dst: str, chars: int) -> None:
remaining = self._limit - self._count
logger.info(
"voipms_send kind=%s did=%s dst=%s chars=%d used_today=%d limit=%d remaining=%d",
kind,
did,
dst,
chars,
self._count,
self._limit,
remaining,
)