Skip to content

smtpconn: Treat wrapped dial errors as network errors - #874

Merged
foxcpp merged 1 commit into
foxcpp:masterfrom
amlaban65:smtpconn-dial-error-temporary
Nov 1, 2026
Merged

foxcpp merged 1 commit into
foxcpp:masterfrom
amlaban65:smtpconn-dial-error-temporary

Conversation

@amlaban65

Copy link
Copy Markdown
Contributor

Fixes #872

Problem

Since fd6d880, attemptConnect wraps dial errors as fmt.Errorf("dialer: %w", err). wrapClientErr recognizes network errors with a type switch on *net.OpError, so a wrapped dial error now falls through to the default case. It is no longer turned into 450 4.4.2 Network I/O error and ends up as a permanent 554 5.0.0 Internal server error. With an LMTP target, maddy rejected RCPT permanently while the LMTP server was restarting, instead of asking the sender to retry.

Change

  • In the default case of wrapClientErr, look for a *net.OpError with errors.As.
  • Move the existing *net.OpError handling into a small wrapNetErr helper, used for both the direct and the wrapped case. The returned SMTPError keeps the full error (including the dialer: context) in Err. The codes and messages are unchanged.

Testing

  • Added TestConnectRefused in internal/smtpconn. It dials a closed local port with both Connect and ConnectLMTP and checks for a temporary 450 4.4.2 SMTPError. It fails on dev without the fix (the error comes back as exterrors.fieldsWrap: dialer: dial tcp ...: connection refused) and passes with it.
  • go test ./internal/smtpconn/... ./internal/target/smtp/ ./internal/target/remote/ passes. gofmt and go vet ./internal/smtpconn/ are clean.
  • I did not run the full test suite or the integration tests in tests/. Locally I used Go 1.25.1 with a temporary modfile (go.mod asks for 1.26), so CI is the check on the real toolchain.

@foxcpp
foxcpp changed the base branch from dev to master November 1, 2026 13:12
Since fd6d880, attemptConnect wraps dial errors as "dialer: %w".
wrapClientErr matched *net.OpError with a type switch, so wrapped dial
errors fell through to the default case and were reported as permanent
"554 5.0.0 Internal server error" instead of "450 4.4.2 Network I/O
error". For LMTP this made maddy reject RCPT permanently while the
LMTP server was restarting, instead of asking the sender to retry.

Look for *net.OpError with errors.As in the default case, so the
wrapped error is classified as before while keeping the added context.

Fixes foxcpp#872

Signed-off-by: Ahmed Laban <amlaban@outlook.com>
@foxcpp
foxcpp force-pushed the smtpconn-dial-error-temporary branch from faa1390 to 1dd7b84 Compare November 1, 2026 13:19
@foxcpp

foxcpp commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Rebased onto master. Bugfix PRs should be created into master, not dev.

@foxcpp
foxcpp merged commit 707eac7 into foxcpp:master Nov 1, 2026
3 checks passed
@foxcpp

foxcpp commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Thanks!

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.

LMTP connection failures return 554 instead of 450 (regression in 0.9.5)

2 participants