Files
Meister-Eder/openspec/changes/email-loop-prevention/tasks.md
T
Claude 514d235a2f docs(openspec): add change artifacts for email-loop-prevention
Creates openspec/changes/email-loop-prevention/ with all artifacts:

- proposal.md: Why (MAILER-DAEMON bounce loop incident), what changes
  (automated sender detection + message-count cap), capabilities affected
- design.md: Two-layer defence rationale, detection signal table, decisions
  on channel/agent boundary split, 20-message cap, one-shot alert, CC-only
  routing; risks and trade-offs documented
- specs/email-channel/spec.md: ADDED requirements for detect_automated_message(),
  is_automated/automated_reason message dict fields, no-reply guarantee
- specs/registration-notifications/spec.md: ADDED requirements for
  notify_loop_escalation() — alert content, one-shot guarantee, CC-only
  routing, dev-mode guard
- tasks.md: All 6 sections fully checked (implementation already complete)

https://claude.ai/code/session_01KwvR5hDPjSuJg4kvw5b5e5
2026-02-26 21:06:53 +00:00

3.6 KiB

1. Automated Sender Detection (email_channel.py)

  • 1.1 Add _AUTOMATED_SENDER_RE regex for known non-human local-parts (mailer-daemon, postmaster, noreply, no-reply, donotreply, bounce, …)
  • 1.2 Add _AUTOMATED_SUBJECT_RE regex for bounce/OOO subject patterns (German + English)
  • 1.3 Implement detect_automated_message(raw_msg, from_addr) → (bool, str) checking all signals in priority order: sender pattern → Auto-Submitted → X-Auto-Response-Suppress → multipart/report → X-Loop → Precedence → subject
  • 1.4 Add is_automated and automated_reason fields to the dict returned by fetch_unread_messages()

2. Poll Loop Guard (main.py)

  • 2.1 In run_poll_loop(), check msg.get("is_automated") before calling agent.process_message()
  • 2.2 If automated: log a warning, call agent.handle_automated_message(), and continue (skip send_reply)

3. Agent — Automated Message Handler (agent/core.py)

  • 3.1 Add MAX_USER_MESSAGES = 20 module-level constant
  • 3.2 Implement handle_automated_message(sender_email, subject, reason, inbound_message_id) method
  • 3.3 In handle_automated_message: load or create state; set loop_escalated = True; call notify_loop_escalation() once; silently skip if already escalated; save state
  • 3.4 In process_message(), after appending the user message, count user messages; if count > MAX_USER_MESSAGES and not escalated: set loop_escalated = True, call notify_loop_escalation(), return ""
  • 3.5 If already escalated and over limit: silently save state and return ""

4. Conversation State (models/conversation.py)

  • 4.1 Add loop_escalated: bool = False field to ConversationState
  • 4.2 Include loop_escalated in to_dict()
  • 4.3 Restore loop_escalated in from_dict() with default False for backward compatibility

5. Admin Notification (notifications/notifier.py)

  • 5.1 Implement notify_loop_escalation(sender_email, conversation_id, reason, message_count) method
  • 5.2 Route alert to self._cc_emails only (not playgroup leaders)
  • 5.3 Subject: [WARNUNG] Automatische E-Mail / Endlosschleife erkannt: {sender_email}
  • 5.4 Body: sender, conversation ID, message count, reason, call-to-action in German
  • 5.5 Guard: if no CC emails configured, log warning and return without SMTP call

6. Tests

  • 6.1 TestDetectAutomatedMessageBySender — mailer-daemon, postmaster, noreply, no-reply, donotreply, bounce; normal parent address not flagged
  • 6.2 TestDetectAutomatedMessageByHeaders — Auto-Submitted (auto-replied, auto-generated, no); X-Auto-Response-Suppress; multipart/report; X-Loop; Precedence bulk/junk; Precedence list not flagged
  • 6.3 TestDetectAutomatedMessageBySubject — Undelivered Mail, Mail Delivery Failed, Out of Office, Abwesenheitsnotiz, Automatische Antwort; case-insensitive; normal subject not flagged
  • 6.4 TestHandleAutomatedMessage — sets loop_escalated; calls notifier once; creates state when none exists; drops silently if already escalated; notifier failure does not propagate; inbound message ID stored
  • 6.5 TestProcessMessageCountCap — at limit still processes; over limit returns ""; sets loop_escalated; calls notifier once; no duplicate alert; notifier failure does not propagate; constant equals 20
  • 6.6 TestNotifyLoopEscalation — sends to CC; [WARNUNG] in subject; sender in subject; reason in body; message count in body; no-CC guard; no-SMTP guard
  • 6.7 TestConversationStateLoopEscalated (test_models.py) — default False; to_dict includes key; True round-trip; from_dict backward compatibility