Every assumption in the top todo item was checked against the live system (2026-08-09). Most are wrong or obsolete.
WRONG. The CNAME is in place and resolving.
Evidence: host mail.critchley.biz returns: "mail.critchley.biz is an alias for gravlax.critchley.biz. gravlax.critchley.biz has address 16.16.198.94"
WRONG. Dovecot is installed, running, and has a valid Let's Encrypt cert for mail.critchley.biz.
Evidence: openssl s_client -connect mail.critchley.biz:993 completes TLS handshake: depth=0 CN=mail.critchley.biz, verify return:1. IMAP greeting: "Dovecot (Debian) ready."
WRONG (implied by above). Dovecot is fully configured — IMAPS port 993, valid cert, virtual users, INBOX accessible. Playbook has been run.
WRONG. popit3 already uses imap_deliver.py which delivers via IMAP APPEND directly to mail.critchley.biz:993.
Evidence: /home/john/py/popit3/imap_deliver.py line 16: IMAP_HOST = 'mail.critchley.biz', port 993. process_emails.py routes envoy@critchley.biz to imap_deliver.deliver_to_envoy.
WRONG / MOOT. The [imap] stunnel section is commented out in /etc/stunnel/stunnel.conf. Envoy connects directly (no stunnel) to mail.critchley.biz:993 via imaplib.IMAP4_SSL().
Evidence: orchestrator.py: IMAP_HOST = 'mail.critchley.biz'. stunnel.conf [imap] section is commented out; active sections are notes (8021), notes-mcp (8023), postgres (5432), socks5h (1080).
WRONG. The mail pipeline is fully functional end-to-end. The missing-reply problem has a different cause.
Evidence: Envoy cron runs every 5 minutes (5am–10pm). Recent log (2026-08-09 09:57): IMAP connects to mail.critchley.biz, finds 17 messages in INBOX, reports "Inbox drain complete: no UNSEEN emails remaining." All 17 are already SEEN.
1. Phase registry warning — every run since at least 2026-07-28: "Could not load phase registry from notes ('str' object has no attribute 'get') — using hardcoded defaults". This is a live bug in orchestrator.py; the note at envoy/states/index (or wherever the registry lives) may be malformed or the fetch is returning a string instead of a dict.
2. 17 SEEN messages in INBOX — unclear if Envoy processed the test emails (2026-07-19, 2026-07-27, 2026-07-31) and failed to reply, or if they arrived already seen. Need to check: (a) are those messages present, (b) what Envoy did when it processed them, (c) whether send_emails ran and if SMTP succeeded.
3. No evidence of outgoing mail — no send_email or SMTP log entries visible in the grep of envoy.log. Either Envoy isn't reaching the send phase, or SMTP is failing silently.
Mark the mail server migration item ✅ Complete (all infrastructure is in place). Replace it with two new items: (1) fix the phase registry bug, (2) diagnose why no SMTP output is seen when Envoy processes emails.
Root cause (two problems): 1. The note envoy/states/index was lost (not in live store, though present in Apr 2026 backup). 2. notes_client.read_doc() returns a plain string on 404 ("Document ... not found") rather than raising an exception. orchestrator.py calls doc.get() on that string, causing AttributeError which the except clause logs as the warning seen in every run since 2026-07-28.
Fix A — Restore envoy/states/index: PUT the note back from backup content. non_terminal_phases: triage, gathering, summarising, working, coding, composing, waiting, inbox_scan.
Fix B — Defensive check in load_phase_registry(): after read_doc(), check isinstance(doc, dict) before calling doc.get(). Log a clear warning and fall back if doc is a string. This makes the function robust against future notes_client API surprises.
Conclusion (2026-08-09): Envoy IS working correctly. A reply was confirmed sent to johnsrcritchley@gmail.com on 2026-08-05 11:17 re ‘Work through open items: ansible/todo’ via Brevo SMTP. Processing complete, stored in Sent.
Root cause of the ‘no reply’ period (2026-07-20 to 2026-08-05): The gdata notes server (gravlax:18021 via stunnel) was intermittently down, producing ‘Connection reset by peer’ and HTTP 500 errors. This caused Fatal error in main before any LLM or SMTP call could be made. 16 emails from that period were processed far enough to receive \Seen + $EnvoyAnalysed flags (during inbox_scan header fetch and body fetch) but not far enough to send replies. They are now permanently excluded from Envoy’s search (which requires UNSEEN + UNKEYWORD $EnvoyAnalysed).
Fix applied (2026-08-09): Restored envoy/states/inbox_scan from Apr 2026 backup (was missing, causing a WARNING on every run since before Aug 5). No code change needed — the stuck emails are old test emails (Feb–Jul 2026) and can be left or deleted. The notes server is currently stable.