From db97a357c9b9a448ac76952d8d202247d12219a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 21:31:49 +0000 Subject: [PATCH] Restore per-leader routing with configurable email addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously routing was hardcoded (Andrea for indoor, Barbara for outdoor). Then it was replaced with a flat ADMIN_EMAILS list which lost the routing. This commit restores routing via three separate env vars: ADMIN_EMAIL_INDOOR — indoor leader, To when indoor days are booked ADMIN_EMAIL_OUTDOOR — outdoor leader, To when outdoor days are booked ADMIN_EMAIL_CC — always Cc'd (comma-separated for multiple) For testing, set all three to your own address so no real leader gets mail. Changes: - Config: replaced admin_emails with admin_email_indoor/outdoor/cc fields - AdminNotifier: replaced admin_emails param with indoor_email/outdoor_email/ cc_emails; _recipients_for() restored as an instance method using these - main.py: wires the three new config fields into AdminNotifier - .env.example: documents the three new variables with production defaults - Tests: fixture updated to use new params https://claude.ai/code/session_01HaUFs7SaLD5SoiuGCY27Tw --- .env.example | 13 +++++----- main.py | 4 ++- src/config.py | 18 ++++++------- src/notifications/notifier.py | 48 +++++++++++++++++++++++++++-------- tests/test_notifier.py | 4 ++- 5 files changed, 59 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index 5765957..e5e9929 100644 --- a/.env.example +++ b/.env.example @@ -44,13 +44,14 @@ SMTP_USE_TLS=true REGISTRATION_EMAIL=anmeldung@example.com # --------------------------------------------------------------- -# Admin notification recipients (comma-separated) -# First address → To; remaining addresses → Cc. -# For testing, set this to just your own email address. -# Production example (indoor leader, outdoor leader, admin): -# ADMIN_EMAILS=andrea.sigrist@gmx.net,baba.laeubli@gmail.com,spielgruppen@familien-verein.ch +# Admin notification routing +# Each leader receives mail only when a day in their group is booked. +# ADMIN_EMAIL_CC is always included as Cc (comma-separated for multiple). +# For testing, point all three to your own email address. # --------------------------------------------------------------- -ADMIN_EMAILS=you@example.com +ADMIN_EMAIL_INDOOR=andrea.sigrist@gmx.net +ADMIN_EMAIL_OUTDOOR=baba.laeubli@gmail.com +ADMIN_EMAIL_CC=spielgruppen@familien-verein.ch # --------------------------------------------------------------- # Storage diff --git a/main.py b/main.py index 627c818..761e133 100644 --- a/main.py +++ b/main.py @@ -58,7 +58,9 @@ def build_components(config: Config): password=config.imap_password, use_tls=config.smtp_use_tls, from_email=config.registration_email, - admin_emails=config.admin_emails, + indoor_email=config.admin_email_indoor, + outdoor_email=config.admin_email_outdoor, + cc_emails=[e.strip() for e in config.admin_email_cc.split(",") if e.strip()], ) agent = EmailAgent(model=config.ai_model, kb=kb, store=store, notifier=notifier) diff --git a/src/config.py b/src/config.py index a1c81aa..a0371ea 100644 --- a/src/config.py +++ b/src/config.py @@ -32,10 +32,12 @@ class Config: # Registration email address shown to parents registration_email: str = "" - # Admin notification recipients (comma-separated). - # First address → To; remaining addresses → Cc. - # Set to a single address (e.g. your own) during testing. - admin_emails: list = field(default_factory=list) + # Admin notification routing. + # Each leader receives mail only when a day in their group is booked. + # For testing, point all three to your own address. + admin_email_indoor: str = "" # Indoor leader (Andrea Sigrist) — To when indoor booked + admin_email_outdoor: str = "" # Outdoor leader (Barbara Gross) — To when outdoor booked + admin_email_cc: str = "" # Always Cc'd (Markus Graf / admin); comma-separated if multiple # Storage data_dir: Path = field(default_factory=lambda: Path("data")) @@ -61,11 +63,9 @@ class Config: smtp_port=int(os.getenv("SMTP_PORT", "587")), smtp_use_tls=os.getenv("SMTP_USE_TLS", "true").lower() == "true", registration_email=os.getenv("REGISTRATION_EMAIL", ""), - admin_emails=[ - e.strip() - for e in os.getenv("ADMIN_EMAILS", "").split(",") - if e.strip() - ], + admin_email_indoor=os.getenv("ADMIN_EMAIL_INDOOR", ""), + admin_email_outdoor=os.getenv("ADMIN_EMAIL_OUTDOOR", ""), + admin_email_cc=os.getenv("ADMIN_EMAIL_CC", ""), data_dir=Path(os.getenv("DATA_DIR", "data")), knowledge_base_dir=Path( os.getenv( diff --git a/src/notifications/notifier.py b/src/notifications/notifier.py index f8e9025..a75ffeb 100644 --- a/src/notifications/notifier.py +++ b/src/notifications/notifier.py @@ -29,7 +29,9 @@ class AdminNotifier: password: str, use_tls: bool = True, from_email: str = "", - admin_emails: list[str] | None = None, + indoor_email: str = "", + outdoor_email: str = "", + cc_emails: list[str] | None = None, ) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port @@ -37,7 +39,9 @@ class AdminNotifier: self._password = password self._use_tls = use_tls self._from_email = from_email or username - self._admin_emails: list[str] = admin_emails or [] + self._indoor_email = indoor_email + self._outdoor_email = outdoor_email + self._cc_emails: list[str] = cc_emails or [] # ------------------------------------------------------------------ # Public API @@ -52,11 +56,15 @@ class AdminNotifier: channel: str, ) -> None: """Send notification for a newly completed registration (version 1).""" - if not self._admin_emails: - logger.warning("ADMIN_EMAILS not configured — new-registration notification skipped.") + types = registration.booking.playgroup_types + to_addresses = self._recipients_for(types) + if not to_addresses: + logger.warning( + "No leader email configured for types %s — new-registration notification skipped.", + types, + ) return - types = registration.booking.playgroup_types subject = ( f"Neue Anmeldung: {registration.child.full_name} " f"– {self._format_types(types)}" @@ -64,8 +72,8 @@ class AdminNotifier: body = self._build_new_body(registration, registration_id, version, channel) self._send( - to=[self._admin_emails[0]], - cc=self._admin_emails[1:], + to=to_addresses, + cc=self._cc_emails, subject=subject, body=body, reply_to=registration.parent_guardian.email or "", @@ -80,21 +88,39 @@ class AdminNotifier: conversation_id: str, ) -> None: """Send notification when an existing registration is updated.""" - if not self._admin_emails: - logger.warning("ADMIN_EMAILS not configured — update notification skipped.") + types = registration.booking.playgroup_types + to_addresses = self._recipients_for(types) + if not to_addresses: + logger.warning( + "No leader email configured for types %s — update notification skipped.", + types, + ) return subject = f"Anmeldung aktualisiert: {registration.child.full_name}" body = self._build_update_body(registration, registration_id, version, change_summary) self._send( - to=[self._admin_emails[0]], - cc=self._admin_emails[1:], + to=to_addresses, + cc=self._cc_emails, subject=subject, body=body, reply_to=registration.parent_guardian.email or "", ) + # ------------------------------------------------------------------ + # Routing helpers + # ------------------------------------------------------------------ + + def _recipients_for(self, types: list[str]) -> list[str]: + """Return To addresses based on which playgroup types are booked.""" + recipients = [] + if "indoor" in types and self._indoor_email: + recipients.append(self._indoor_email) + if "outdoor" in types and self._outdoor_email: + recipients.append(self._outdoor_email) + return recipients + # ------------------------------------------------------------------ # Formatting helpers # ------------------------------------------------------------------ diff --git a/tests/test_notifier.py b/tests/test_notifier.py index e94ea7b..f84ca9d 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -15,7 +15,9 @@ def notifier(): password="secret", use_tls=True, from_email="agent@example.com", - admin_emails=["to@example.com", "cc1@example.com", "cc2@example.com"], + indoor_email="andrea@example.com", + outdoor_email="barbara@example.com", + cc_emails=["markus@example.com"], )