TLS-RPT reports from Google rejected: empty "sending-mta-ip" causes "invalid IP address syntax"

Issue Description

Since I moved my domain to Stalwart on 15 October, the daily TLS-RPT reports from Google ([email protected]) that contain failure details are rejected. In the failure-details section Google sends “sending-mta-ip”: “” (an empty string). The parser fails on this field and the whole report is dropped, so I lose exactly the reports that describe failed sessions. I read them manually from the mailbox for now.

Expected Behavior

An empty or unparseable “sending-mta-ip” (and “receiving-ip”) should be treated as missing, and the rest of the report should be processed normally. Strictly speaking, RFC 8460 expects an IP address in this field, so Google’s empty string is not fully valid, but other parsers accept it (parsedmarc fixed the same case, see domainaware/parsedmarc#915). Since Google sends these reports to every domain with TLS-RPT enabled, accepting them seems the practical choice.

Actual Behavior

The report is rejected with incoming-report.tls-rpc-parse-failed and the reason “invalid IP address syntax at line 1 column 588”. The report never reaches the TLS report analysis.

Reproduction Steps

  1. Enable TLS-RPT for a domain and receive reports on an address handled by Stalwart (inbound report addresses).
  2. Receive a TLS-RPT report from Google (application/tlsrpt+gzip) whose failure-details contain an empty “sending-mta-ip”. An anonymized copy of a real report:
{
  "organization-name": "Google Inc.",
  "date-range": {"start-datetime": "2026-10-15T00:00:00Z", "end-datetime": "2026-10-15T23:59:59Z"},
  "contact-info": "[email protected]",
  "report-id": "2026-10-15T00:00:00Z_example.com",
  "policies": [{
    "policy": {
      "policy-type": "sts",
      "policy-string": ["version: STSv1", "mode: enforce", "mx: mx.example.net", "max_age: 604800"],
      "policy-domain": "example.com",
      "mx-host": ["mx.example.net"]
    },
    "summary": {"total-successful-session-count": 0, "total-failure-session-count": 9},
    "failure-details": [{
      "result-type": "validation-failure",
      "sending-mta-ip": "",
      "receiving-ip": "192.0.2.25",
      "receiving-mx-hostname": "mail.example.com",
      "failed-session-count": 9
    }]
  }]
}
  1. Check the log: the report is rejected with incoming-report.tls-rpc-parse-failed.

Relevant Log Output

2026-10-17T13:45:02Z INFO Failed to parse TLS RPC report (incoming-report.tls-rpc-parse-failed) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.210.69, remotePort = 55375, from = “[email protected]”, reason = “ReportParseError(“invalid IP address syntax at line 1 column 588”)”, causedBy = “crates/smtp/src/reporting/analysis.rs:250”
2026-10-16T14:25:35Z INFO Failed to parse TLS RPC report (incoming-report.tls-rpc-parse-failed) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.216.70, remotePort = 49195, from = “‘[email protected]’”, reason = “ReportParseError(“invalid IP address syntax at line 1 column 588”)”, causedBy = “crates/smtp/src/reporting/analysis.rs:250”

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

The problem seems to be in mail-auth 0.13.3: FailureDetails.sending_mta_ip and receiving_ip are Option, so an empty string fails deserialization instead of becoming None. I can send more sample reports if that helps.

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Thanks for the detailed report and the bisected root cause. This matches what the event reference describes: incoming-report.tls-rpc-parse-failed is logged whenever the JSON payload of a TLS-RPT report can’t be deserialized, and the whole report is then discarded rather than partially processed.

You’re right that this traces back to how sending-mta-ip/receiving-ip are typed as Option<IpAddr> in the TLS-RPT parsing dependency: a present-but-empty string fails IpAddr deserialization instead of being coerced to None, so serde rejects the entire report. This is a parsing bug rather than a configuration issue, so there’s nothing you can change in Stalwart’s settings to work around it (no “lenient parsing” toggle exists in the TLS-RPT report ingestion). A developer will need to update the deserialization of those fields to treat empty strings as absent.

This isn’t a config question I can resolve from the docs, so this report needs follow-up from a staff/community developer to patch the report parsing code.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Fixed in v1.0.0 (expected at the end of this month/early November).