""" Email integration configuration. Reads from the same env-var surface as the rest of the CRM (server.py style), no pydantic/dotenv magic — stdlib only. """ import os from dataclasses import dataclass from typing import Optional # Reuse the CRM's data dir so backups and email storage live together. _PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) _DEFAULT_DATA_DIR = os.path.join(_PROJECT_DIR, "data") def _bool_env(name: str, default: bool = False) -> bool: v = os.environ.get(name, "").strip().lower() if v in ("1", "true", "yes", "on"): return True if v in ("0", "false", "no", "off"): return False return default def _int_env(name: str, default: int) -> int: try: return int(os.environ.get(name, str(default))) except (TypeError, ValueError): return default @dataclass(frozen=True) class EmailConfig: # Master kill switch. When False, scheduler doesn't start and routes # return 503. Migrations are still applied (so schema is ready). enabled: bool # Primary auth path. "dwd" means service account / domain-wide delegation. # "oauth" means per-user refresh tokens. DWD is default; OAuth is the # pluggable fallback. primary_auth: str # DWD specifics dwd_key_path: Optional[str] workspace_domain: Optional[str] # OAuth specifics (used for fallback + admin UI) oauth_client_id: Optional[str] oauth_client_secret: Optional[str] oauth_redirect_uri: Optional[str] # Encryption key (base64) for OAuth refresh-token-at-rest encryption. # Required whenever oauth path is in use. DWD path never persists tokens. secret_key_b64: Optional[str] # Sync scheduling sync_interval_sec: int backfill_page_size: int max_attachment_mb: int max_parallel_attachment_downloads: int # Storage data_dir: str attachments_dir: str secrets_dir: str # Rate limit / retry rate_limit_units_per_sec_per_account: int retry_max_attempts: int retry_initial_delay_sec: float retry_max_delay_sec: float # Gmail history retention — if we fall this far behind, switch to date # backfill since Gmail may have pruned history records. history_stale_days: int def load() -> EmailConfig: data_dir = os.environ.get("CRM_DATA_DIR", _DEFAULT_DATA_DIR) return EmailConfig( enabled=_bool_env("CRM_GMAIL_INTEGRATION_ENABLED", False), primary_auth=os.environ.get("CRM_GMAIL_AUTH_METHOD", "dwd").lower(), dwd_key_path=os.environ.get("CRM_GMAIL_SA_KEY_PATH") or None, workspace_domain=os.environ.get("CRM_GMAIL_WORKSPACE_DOMAIN") or None, oauth_client_id=os.environ.get("CRM_GMAIL_OAUTH_CLIENT_ID") or None, oauth_client_secret=os.environ.get("CRM_GMAIL_OAUTH_CLIENT_SECRET") or None, oauth_redirect_uri=os.environ.get("CRM_GMAIL_OAUTH_REDIRECT_URI") or None, secret_key_b64=os.environ.get("CRM_GMAIL_SECRET_KEY") or None, sync_interval_sec=_int_env("CRM_GMAIL_SYNC_INTERVAL_MIN", 180) * 60, backfill_page_size=_int_env("CRM_GMAIL_BACKFILL_PAGE_SIZE", 500), max_attachment_mb=_int_env("CRM_GMAIL_MAX_ATTACHMENT_MB", 50), max_parallel_attachment_downloads=_int_env("CRM_GMAIL_ATTACH_CONCURRENCY", 4), data_dir=data_dir, attachments_dir=os.path.join(data_dir, "email_attachments"), secrets_dir=os.path.join(data_dir, "secrets"), rate_limit_units_per_sec_per_account=_int_env("CRM_GMAIL_RATE_UNITS_SEC", 150), retry_max_attempts=_int_env("CRM_GMAIL_RETRY_MAX", 5), retry_initial_delay_sec=float(os.environ.get("CRM_GMAIL_RETRY_INITIAL_SEC", "1.0")), retry_max_delay_sec=float(os.environ.get("CRM_GMAIL_RETRY_MAX_SEC", "60.0")), history_stale_days=_int_env("CRM_GMAIL_HISTORY_STALE_DAYS", 5), ) # Singleton. Reload with `reload_config()` if env changes (mostly for tests). CONFIG = load() def reload_config() -> EmailConfig: global CONFIG CONFIG = load() return CONFIG