Skip to content

fix: repair two broken error paths in the sender - #498

Merged
andris9 merged 1 commit into
masterfrom
fix/sender-error-handling-cleanup
Jul 19, 2026
Merged

fix: repair two broken error paths in the sender#498
andris9 merged 1 commit into
masterfrom
fix/sender-error-handling-cleanup

Conversation

@andris9

@andris9 andris9 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Two correctness fixes in the sender's error-handling paths, both found during review of #497.

1. Unexpected-close handler discarded its own error object

The setTimeout that fires on an unexpected clean connection close builds a synthetic error (category: 'network', temporary: true) but then called handleError(delivery, connection, err) — passing err, the getConnectionWithCache callback param, which is guaranteed falsy past its own if (err) return guard. So `Network error: ${err.message}` threw a TypeError inside an unref()'d timer instead of deferring the delivery. The constructed error was never used. Broken since #363 (Jan 2024).

Fix: pass the constructed error and read error.message.

2. has-a-code guard tested the raw response, not the normalized one

In handleResponseError, the "does this carry an SMTP code" guard tested the raw string with /^\d{3}\b/, while bounces.check() classifies the normalized string (formatSMTPResponse trims a leading CRLF/space ahead of the code). A 5xx arriving with leading whitespace — e.g. a cache-restored response, where responseCode is not restored — failed the raw test, fell into the network-defer branch, and deferred forever instead of rejecting.

Fix: hoist the smtpResponse normalization (already computed ~15 lines lower for logging) above the branch and test that, so the guard and the classifier consume the same string. Net −1 line; removes a duplicate formatSMTPResponse call.

Verified

  • Reproduced Modify message and send to specify server #1: the old binding throws TypeError: Cannot read properties of null (reading 'message'); the fix builds the error and routes it as a network defer through the real handleResponseError.
  • Drove the real handleResponseError for TypeScript #2 with the cache-restore shape (response set, no responseCode): \r\n550 ... now rejects and \r\n554 ... not allowed to connect now defers+blacklists, where both previously deferred forever. Non-whitespace cases unchanged.
  • New bounces-test.js cases lock in the load-bearing property (formatSMTPResponse strips leading CRLF/space) that the guard now depends on. Suite + eslint green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GFF2LP1xQR2yV9DB37wPyD

Unexpected-close handler: the setTimeout that fires on an unexpected clean
close built a synthetic `error` (network/temporary) but then called
handleError() with the getConnectionWithCache callback param `err`, which is
guaranteed falsy past its own guard -- so `${err.message}` threw a TypeError
inside the unref'd timer instead of deferring the delivery. Pass the
constructed error. Broken since #363.

handleResponseError has-a-code guard: it tested the raw response with
/^\d{3}\b/ while bounces.check() classifies the normalized response
(formatSMTPResponse trims a leading CRLF/space ahead of the code). A 5xx that
arrived with leading whitespace -- e.g. a cache-restored response, where
responseCode is not restored -- failed the raw test, took the network-defer
branch, and deferred forever instead of rejecting. Hoist the smtpResponse
normalization (already computed lower down) above the branch and test that, so
the guard and the classifier see the same string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFF2LP1xQR2yV9DB37wPyD
@andris9
andris9 merged commit 1d9c933 into master Jul 19, 2026
8 checks passed
@andris9
andris9 deleted the fix/sender-error-handling-cleanup branch July 19, 2026 09:51
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