Bug fixes
parse_email()no longer crashes withKeyError: 'Headers'on messages whoseFromheader is present but empty/unparseable (e.g. a bareFrom:line). mailparser omits"from"frommail_jsonfor such messages, and the fallback readparsed_email["Headers"]— a key that is never set; the parsed headers are stored under lowercase"headers"(see the assignment at the top ofparse_email()). The fallback now reads the correct key and treats an empty parsed header list the same as a missing header, yieldingfrom=None.- A failed IPinfo API token probe no longer logs "IPinfo API configured".
configure_ipinfo_api(..., probe=True)documents that non-fatal probe errors are logged as warnings with the token still accepted, but_ipinfo_api_lookup()returnsNoneon network errors instead of raising, so the probe's exception handler never fired and a probe that couldn't reach the API logged the success message. The probe now checks the lookup result and logs a warning when verification failed. Invalid tokens (401/403) still raiseInvalidIPinfoAPIKey. - Aggregate-report record timestamps are no longer skewed by the host's UTC offset in the Elasticsearch, OpenSearch, and Splunk HEC outputs (#819). A record's
interval_begin/interval_endare UTC wall-clock strings (converted to UTC at parse time), but the ES/OpenSearch save paths re-parsed them as local time, shifting the storeddate_begin/date_endfields and the daily/monthly index date by the host's UTC offset on any non-UTC host, and the Splunk HEC output computed the aggregate eventtimethe same way. The three call sites now passassume_utc=True, the same treatmentarrival_date_utcreceived in the #811 fix. The report-levelbegin_date/end_dateparses (ES/OpenSearch dedup query, PostgreSQL output) were investigated and left unchanged — those strings are genuinely local time, so their existing round-trip is already correct.
Cleanup
- Removed dead code found while extending test coverage: the unused
_SMTPTLSReportDoc.add_policy()helpers in the Elasticsearch and OpenSearch outputs (the save paths construct policy documents directly), a no-opfailure_indexesloop in bothmigrate_indexes()implementations (the parameter is still accepted; no failure-index migrations are currently needed), an unreachableimportlib.resourcesImportError fallback inparsedmarc.utils(it re-imported the same module, andimportlib.resources.filesalways exists on the supported Python ≥3.10), and an unreachable "Invalid report content" guard inextract_report()(every input branch either assigns the file object or raises first, confirmed by pyright narrowing).