"""What the relay needs to know, and what it refuses to start without. The bearer token is the only thing standing between this service and an open spam relay, so an unset token is a startup failure rather than a service that accepts everything. Provider credentials are the opposite: a missing SendGrid key must not stop SMS working, so each channel is asked at send time whether it is configured and answers for itself. """ from __future__ import annotations import os from dataclasses import dataclass, field def _env(name: str, default: str = "") -> str: return (os.environ.get(name, default) or "").strip() @dataclass(frozen=True) class Settings: # The shared secret every caller must present. No default: a relay that # falls back to a well-known token is worse than one that will not boot. token: str = field(default_factory=lambda: _env("CHANNEL_EXIT_TOKEN")) sendgrid_key: str = field(default_factory=lambda: _env("SENDGRID_API_KEY")) # SendGrid rejects a From that is not a verified sender, so this is an # operator setting and never anything a caller can influence. email_from: str = field(default_factory=lambda: _env("EMAIL_FROM")) twilio_sid: str = field(default_factory=lambda: _env("TWILIO_ACCOUNT_SID")) twilio_token: str = field(default_factory=lambda: _env("TWILIO_AUTH_TOKEN")) twilio_from: str = field(default_factory=lambda: _env("TWILIO_FROM")) timeout: float = field(default_factory=lambda: float(_env("PROVIDER_TIMEOUT", "15") or 15)) @property def email_ready(self) -> bool: return bool(self.sendgrid_key and self.email_from) @property def sms_ready(self) -> bool: return bool(self.twilio_sid and self.twilio_token and self.twilio_from) def require_token(self) -> None: if not self.token: raise RuntimeError("CHANNEL_EXIT_TOKEN is unset; refusing to run an unauthenticated relay") def get_settings() -> Settings: return Settings()