Skip to content

Fix Reply-To parsing and RUF dashboards (release 10.0.3)#784

Merged
seanthegeek merged 5 commits into
fix/mailsuite-2.2.1-empty-addressfrom
fix/failure-reply-to-and-dashboards
May 24, 2026
Merged

Fix Reply-To parsing and RUF dashboards (release 10.0.3)#784
seanthegeek merged 5 commits into
fix/mailsuite-2.2.1-empty-addressfrom
fix/failure-reply-to-and-dashboards

Conversation

@seanthegeek
Copy link
Copy Markdown
Contributor

@seanthegeek seanthegeek commented May 24, 2026

Stacked on #783 (base will retarget to master once #783 merges). Release 10.0.3.

Follow-up to #783. The mailsuite 2.2.1 bump fixed the upstream phantom empty-address junk, but parsedmarc-side bugs remained that left Reply-To and From empty/wrong in the failure (RUF) dashboards. This PR fixes them across all four dashboards and makes address rendering uniform.

1. Reply-To/Delivered-To always dropped (parser)

parse_email() looked up mailparser's underscored reply_to / delivered_to keys, but mail_json names those headers reply-to / delivered-to. The lookup always missed, so parsed_sample["reply_to"] was always [] regardless of the message. Now reads the hyphenated keys and stores under the underscored name consumers expect, dropping the raw key so the body carries a single representation (matching to/cc/bcc). Independent of the mail-parser version — 4.2.1 still names the key reply-to.

This one fix flows everywhere parsed_sample is consumed: JSON/CSV output, the Elasticsearch/OpenSearch nested sample.reply_to, and the Postgres dmarc_failure_sample_address table (address_type='reply_to').

2. Uniform address rendering (works on historical data)

Every displayed address (From, To, Reply-To) now renders the same way on every dashboard: Display Name <addr>, or the bare address when there is no display name. The format is assembled at query time from display_name / address — fields that already exist on previously-indexed reports — so the panels render historical data, not only reports stored after upgrading. No new stored field is introduced.

One unavoidable exception: a report's Reply-To only appears for reports parsed by 10.0.3+. Earlier versions discarded it at parse time (§1), so it is absent from older stored reports; only re-parsing the originals recovers it.

3. Dashboards

  • Splunk: the email-samples panel renamed parsed_sample.headers.from{}{} / ...reply-to{}{} — mis-cased and array-of-array shaped, so the columns were empty. Now builds from and reply_to with an eval that coalesces display_name <address> → bare address. (A multi-address Reply-To falls back to addresses-only — a Splunk multi-value-rendering limitation, not data loss.)
  • OpenSearch: the reply_to column aggregated sample.headers.in-reply-to.keyword — the In-Reply-To threading Message-ID, not Reply-To. Repointed to sample.headers.reply-to.keyword, field added to the dmarc_f* index pattern. The ES/OS failure writer now flattens Reply-To into sample.headers["reply-to"], mirroring the existing From/To flattening.
  • Grafana (Elasticsearch): the Failure Samples panel already read sample.headers.reply-to.keyword, but that field held the raw [[name, addr]] array until the writer flattening above. The existing ReplyTo column now renders a clean string — no dashboard change required.
  • Grafana (PostgreSQL): the Failure Reports panel surfaced neither the header From nor Reply-To (only envelope Mail From / Rcpt To). Added a From column (from sample_from) and a Reply To column (aggregated from dmarc_failure_sample_address), both in the same Name <addr> format.

Tests

  • parse_email() with single + multiple Reply-To → all captured; Delivered-To → captured.
  • ES + OS failure writers: assert on the document handed to .save()sample.headers["reply-to"] is the flattened string and nested sample.reply_to carries the address.
  • Postgres: a parsed Reply-To is INSERTed with address_type='reply_to'.

Full suite: 633 passed. ruff check / ruff format --check clean.

Not verified against a live instance

The Splunk / OpenSearch / Grafana dashboard edits were verified structurally (field paths, parsed visState/index-pattern, valid SQL, XML re-parses, SPL un-escapes), not rendered against live backends. Re-import the dashboards (or refresh the dmarc_f* index pattern) to pick up the new OpenSearch field.

🤖 Generated with Claude Code

seanthegeek and others added 5 commits May 24, 2026 13:02
parse_email() read mailparser's underscored reply_to/delivered_to keys,
but mail_json names those headers reply-to/delivered-to, so every
Reply-To/Delivered-To address was silently dropped (reply_to was always
[]). Read the hyphenated keys and store under the underscored name
consumers expect, dropping the raw key so the body carries a single
representation (matching to/cc/bcc).

Dashboards:
- Splunk failure dashboard showed empty from/reply_to columns (mis-cased,
  array-of-array header paths). Now reads structured
  parsed_sample.from.address, parsed_sample.subject,
  parsed_sample.reply_to{}.address.
- OpenSearch failure dashboard's "reply_to" column aggregated
  sample.headers.in-reply-to.keyword (the In-Reply-To threading header).
  Repointed to sample.headers.reply-to.keyword and added that field to the
  dmarc_f* index pattern. The ES/OS failure writer now flattens the
  Reply-To header to a display string on sample.headers["reply-to"],
  mirroring From/To.

Tests assert on the indexed document and on parsed reply_to addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Grafana Elasticsearch dashboard's Failure Samples panel already read
sample.headers.reply-to.keyword, but that field held the raw [[name, addr]]
array until the failure-writer flattening added in this PR; no dashboard
change is needed there — the existing ReplyTo column now renders a clean
string.

The Grafana PostgreSQL dashboard's Failure Reports panel surfaced neither
the message From header nor Reply-To (only the envelope Mail From / Rcpt To).
Add a "From" column (from sample_from JSONB) and a "Reply To" column
(aggregated from dmarc_failure_sample_address where address_type='reply_to',
the path parse_email now populates).

Add a postgres regression test asserting a parsed Reply-To address is
inserted with address_type 'reply_to'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a derived `display` field to every parsed address dict — "Display
Name <addr>", or the bare address when there is no display name — and
point the Splunk failure dashboard's From/Reply-To columns at it
(parsed_sample.from.display, parsed_sample.reply_to{}.display).

The Elasticsearch/OpenSearch and Grafana-ES panels already render this
format via the flattened sample.headers.* fields, and the Grafana-PG
panel builds it in SQL; Splunk reads the structured parsed_sample, so it
needed a ready-to-render field rather than fragile multivalue SPL.

EmailAddress TypedDict and the parse_email_address tests updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous revision pointed the From column at parsed_sample.from.display,
a field that only exists on reports parsed by 10.0.3+. Reports indexed by
earlier versions carry parsed_sample.from.{display_name,address} but no
.display, so their From rendered blank.

The column now coalesces parsed_sample.from.display -> a display_name/address
pairing -> the bare address, so it renders the same "Name <addr>" / address
form on both historical and new data. The < / > are XML-entity-escaped in the
SimpleXML query.

Reply-To has no such fallback by design: earlier versions dropped it at parse
time (parsed_sample.reply_to was always []), so older reports have no Reply-To
to display regardless of query; it appears only for reports parsed by 10.0.3+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review, remove the parse_email_address `display` field (and its
EmailAddress TypedDict entry and tests). The dashboards now assemble the
"Name <addr>" / bare-address form entirely at query time from display_name
and address — fields present on historical and new reports alike — so no
stored field is required and pre-10.0.3 data renders too.

The Splunk failure panel builds both `from` and `reply_to` with an eval that
coalesces `display_name <address>` down to the bare address. A multi-address
Reply-To degrades to addresses-only (a Splunk multi-value concat limitation,
not data loss); the common 0/1-address case renders fully.

ES/OS/Grafana-ES (flattened sample.headers.*) and Grafana-PG (sample_from /
dmarc_failure_sample_address) already build the same form without the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@seanthegeek seanthegeek merged commit aab450f into fix/mailsuite-2.2.1-empty-address May 24, 2026
seanthegeek added a commit that referenced this pull request May 24, 2026
PR #784 was stacked on the #783 branch and its base was never retargeted to
master, so it merged into fix/mailsuite-2.2.1-empty-address instead of master.
master therefore has 10.0.2 (#783's squash) but is missing the 10.0.3 changes.

This re-lands exactly that delta — the Reply-To/Delivered-To parser fix, the
ES/OS Reply-To header flattening, and the Splunk/OpenSearch/Grafana failure
dashboard fixes, with the version bumped to 10.0.3. No mailsuite re-bump (the
>=2.2.1 floor is already on master from 10.0.2).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@seanthegeek seanthegeek deleted the fix/failure-reply-to-and-dashboards branch May 24, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant