From 53640c6990cb084b73db06caaf739c3b859e1010 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 2 Oct 2026 15:59:20 +0200 Subject: [PATCH] fix(smtp): reject the relay when the outgoing server cannot be reached SMTPConnection only calls the connect() callback once the handshake succeeds; a refused or timed out connection is emitted as an 'error' event, which was only logged. relay() then never settled, so its finally block (which now releases the email stream) never ran, auto-relay held the incoming SMTP transaction open without a reply, and the REST relay request hung. Reject the connect promise from the 'error' handler. --- .changeset/fix-relay-connect-error.md | 9 +++++++++ packages/smtp/src/__tests__/relay.test.ts | 11 +++++++++++ packages/smtp/src/relay.ts | 2 ++ 3 files changed, 22 insertions(+) create mode 100644 .changeset/fix-relay-connect-error.md diff --git a/.changeset/fix-relay-connect-error.md b/.changeset/fix-relay-connect-error.md new file mode 100644 index 0000000..a197def --- /dev/null +++ b/.changeset/fix-relay-connect-error.md @@ -0,0 +1,9 @@ +--- +'@maildev/smtp': patch +'maildev': patch +--- + +Fail the relay when the outgoing server cannot be reached. The connection +error was only logged, so the relay never finished: with auto-relay on, the +SMTP client sending to MailDev never got its reply, and the manual relay +request never answered. diff --git a/packages/smtp/src/__tests__/relay.test.ts b/packages/smtp/src/__tests__/relay.test.ts index 1e0a2ff..89f1e97 100644 --- a/packages/smtp/src/__tests__/relay.test.ts +++ b/packages/smtp/src/__tests__/relay.test.ts @@ -147,6 +147,17 @@ describe('relay raw email stream', () => { expect(stream.destroyed).toBe(true) }) + it('closes the stream when the outgoing server cannot be reached', async () => { + const client = new RelayClient({ host: '127.0.0.1', port: await freePort() }, false) + const stream = createReadStream(emlPath) + + const result = await client.relay(email, stream) + + expect(result.success).toBe(false) + expect(result.error).toBeInstanceOf(Error) + expect(stream.destroyed).toBe(true) + }) + it('closes the stream when the outgoing server rejects the recipient', async () => { const port = await freePort() const rejecting = new SMTPServerLib({ diff --git a/packages/smtp/src/relay.ts b/packages/smtp/src/relay.ts index fcb982e..2cd9f33 100644 --- a/packages/smtp/src/relay.ts +++ b/packages/smtp/src/relay.ts @@ -160,8 +160,10 @@ export class RelayClient { debug: false, }) + // A failed connection is reported here, not through the connect callback this.client.on('error', (err: Error) => { this.logger.error('SMTP Connection error:', err) + reject(err) }) this.client.connect((err?: Error) => {