fix: IPv4 preference + DNS cache fallback for stable POP3/IMAP connections

- Add DNS_CACHE_FALLBACK_ENABLED setting (default: true)
- Add in-process DNS cache (_dns_cache, _dns_cache_lock, _get/_set_cached_ipv4)
- Add _resolve_ipv4 (async) and _resolve_ipv4_sync: AF_INET lookup, cache on
  success, return cached IP on EAI_AGAIN when fallback enabled
- Add _IMAP4SSLwithSNI: connects to pre-resolved IPv4 but uses original
  hostname for TLS SNI/cert verification
- Add _POP3SSLWithIPv4Pref / _POP3WithIPv4Pref: override _create_socket to
  use pre-resolved IPv4 while keeping original host for POP3_SSL SNI
- Add _make_pop3_conn factory and _make_imap_client async factory
- Wire IPv4/cache helpers into all 6 connection call-sites (IMAP x3, POP3 x3)
- Distinguish transient DNS (EAI_AGAIN) from permanent in _format_connection_error
- Replace asyncio.get_event_loop() with get_running_loop() in 4 async sites
- All 75 existing unit tests pass unchanged

Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/be0a3bbd-1af1-408e-b918-84753723154b

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-03 20:49:51 +00:00
committed by GitHub
parent 89ebdd2b79
commit 45a2dd8be2
4 changed files with 366 additions and 71 deletions
+23
View File
@@ -30,6 +30,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- **IPv4 preference for all POP3 and IMAP connections**: The app now resolves
every mail-server hostname to an IPv4 address before connecting. This
prevents `ENETUNREACH` / `Network is unreachable` errors and IMAP/POP3
connection timeouts that occur on Docker hosts where IPv6 traffic is not
routed to the internet but dual-stack DNS returns AAAA records first.
- **In-process DNS cache with automatic fallback** (`DNS_CACHE_FALLBACK_ENABLED`,
enabled by default): The last successfully resolved IPv4 address for each
mail-server hostname is stored in memory. When a subsequent DNS lookup fails
with `EAI_AGAIN` (Temporary failure in name resolution) the cached address is
used instead, keeping mail delivery alive through transient resolver outages.
Set `DNS_CACHE_FALLBACK_ENABLED=false` in the environment to disable.
- **Friendly error messages**: Introduced `_format_connection_error()` helper in
`mail_processor.py` that translates raw OS/socket/SSL/POP3/IMAP exceptions into
human-readable sentences including the host:port and actionable guidance (DNS
@@ -58,6 +71,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **Transient DNS errors now produce a clear, accurate message**: `EAI_AGAIN`
errors (errno -3 resolver temporarily unavailable) are now reported as
"Temporary DNS failure while resolving '…'" instead of the misleading "check
that the server address is correct" message that was shown for all DNS errors.
- **`asyncio.get_event_loop()` replaced with `asyncio.get_running_loop()`** in
`_test_pop3_connection`, `_fetch_pop3_emails`, `post_process_pop3`, and
`forward_email`, silencing deprecation warnings in Python ≥ 3.10 and avoiding
potential `DeprecationWarning → RuntimeError` in a future Python release.
- **Empty IMAP error messages** — `IMAP fetch error:` with a blank suffix was
caused by `asyncio.TimeoutError` and `aioimaplib.Abort` having an empty
`str()`. The new `_format_connection_error()` helper always produces a