Log entry for rejecting mail is different then the reason received by sender

Issue Description

SPF Verification is EHLO relaxed and MAIL FROM strict.
When receiving an email from a domain without SPF it is mention in log that it was reject because was too large.

Expected Behavior

I was expecting to see the MAIL FROM line before line SPF From check failed and a line mentioning that the email was rejected because of SPF failed not because the message was too large.
If who is reading can very easily blame the size of the email instead of the real reason problems with SPF configuration of the remote domain.

Actual Behavior

Sender is receiving the real reason SPF rejected but in the log is mentioning Mail too large after SPF From check failed. SPF From check failed line it will be written to the logs even the email is accepted (if SPF Verification->MAIL FROM is set as relaxed.

Reproduction Steps

MTA->Sender Authentication->SPT Verification:

  • EHLO: if local_port == 25 then relaxed
    else disable
  • MAIL FROM: if local_port == 25 then strict
    else disable

Relevant Log Output

2026-07-01T09:04:21Z INFO SMTP EHLO command (smtp.ehlo) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616, domain = “mail-ej1-f42.google.com
2026-07-01T09:04:21Z INFO SMTP EHLO command (smtp.ehlo) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510, domain = “mail-ed1-f42.google.com
2026-07-01T09:04:21Z INFO SPF EHLO check failed (smtp.spf-ehlo-fail) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616, domain = “mail-ej1-f42.google.com”, result = No SPF record (spf.none), elapsed = 31ms
2026-07-01T09:04:21Z INFO SPF EHLO check failed (smtp.spf-ehlo-fail) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510, domain = “mail-ed1-f42.google.com”, result = No SPF record (spf.none), elapsed = 33ms
2026-07-01T09:04:22Z INFO TLS handshake (tls.handshake) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616, listenerId = “smtp”, version = “TLSv1_3”, details = “TLS13_AES_256_GCM_SHA384”
2026-07-01T09:04:22Z INFO TLS handshake (tls.handshake) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510, listenerId = “smtp”, version = “TLSv1_3”, details = “TLS13_AES_256_GCM_SHA384”
2026-07-01T09:04:22Z INFO IPREV check passed (smtp.iprev-pass) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510, domain = “mail-ed1-f42.google.com”, result = IPREV check passed (iprev.pass) { details = [“mail-ed1-f42.google.com.”] }, elapsed = 84ms
2026-07-01T09:04:22Z INFO IPREV check passed (smtp.iprev-pass) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616, domain = “mail-ej1-f42.google.com”, result = IPREV check passed (iprev.pass) { details = [“mail-ej1-f42.google.com.”] }, elapsed = 91ms
2026-07-01T09:04:22Z INFO SPF From check failed (smtp.spf-from-fail) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510, domain = “mail-ed1-f42.google.com”, from = “[email protected]”, result = No SPF record (spf.none), elapsed = 36ms
2026-07-01T09:04:22Z INFO SPF From check failed (smtp.spf-from-fail) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616, domain = “mail-ej1-f42.google.com”, from = “[email protected]”, result = No SPF record (spf.none), elapsed = 32ms
2026-07-01T09:04:22Z INFO Message too large (smtp.message-too-large) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.208.42, remotePort = 48510
2026-07-01T09:04:22Z INFO Message too large (smtp.message-too-large) listenerId = “smtp”, localPort = 25, remoteIp = 209.85.218.42, remotePort = 48616

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

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

Looking at the source for handle_mail_from (crates/smtp/src/inbound/mail.rs), the message-size check (the SIZE= parameter announced in the MAIL FROM command) is evaluated before the SPF check in the code path for each individual MAIL FROM command:

  1. mt_priority handling
  2. Message-size check (SmtpEvent::MessageTooLarge, returns 552 5.3.4 Message too big for system. immediately if from.size exceeds session.data.max-message-size)
  3. hold_for / future-release handling
  4. SPF verification (SmtpEvent::SpfFromFail / SmtpEvent::SpfFromPass, and only rejects with 550 5.7.23 SPF validation failed... if spf_mail_from is set to strict)

An SPF failure with spf_mail_from set to strict does reject that particular MAIL FROM with a 550 response and clears mail_from, but it does not close the connection, so a sending server can (and Gmail’s MTA does) retry MAIL FROM on the same session. If that retry declares a SIZE= value over your configured limit, the size check fires and returns its own 552 before the code ever reaches the SPF block again for that attempt. That produces exactly the pattern in your log: an earlier SPF From check failed entry from one MAIL FROM attempt, followed later by a Message too large entry that belongs to a subsequent attempt in the same connection, which is why they read as if the reason “flipped.” The final SMTP response your sender actually received for the message that ultimately got a 552 (too large) really was rejected for size on that specific try; it’s a separate transaction attempt from the one that failed SPF, not the SPF check being mislabeled.

If you want to confirm this on your setup, correlate log lines by remoteIp+remotePort and look for whether a smtp.mail-from success/failure event appears between the SPF and size log lines (Gmail’s retries reuse the same TCP connection/port, so timestamps will be close together).

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

Hi,

For sure the email was less then maximum size and the sender received a 5.7.23 SPF validation failed.

On the other hand that is not the problem because this was how it was configured. The problem is that I didn’t see any MAIL FROM line and the reason of the rejection could be interpreted as “Message too large” because this is the last entry. SPF verification check can be interpreted just as a warning not the real reason for the rejection.

Fixed in v0.16.16 (yet to be released).