feat: add --outgoing-ignore-tls to keep the relay off STARTTLS (#377)

The relay connection only had `secure`, so with `secure: false` nodemailer
still upgraded to STARTTLS whenever the server advertised it, and there was
no way to talk cleartext to such a server. Pass nodemailer's `ignoreTLS`
through as `ignoreTLS` on the relay config, wired to
--outgoing-ignore-tls / MAILDEV_OUTGOING_IGNORE_TLS / `outgoingIgnoreTls`.
This commit is contained in:
dualfroz committed 2026-10-03 02:24:38 +02:00
1 parent a9a83f64f1
commit fd864f1379
13 files changed
+124

No files matched your search

@@ -0,0 +1,80 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { AddressInfo } from 'node:net'
import nodemailer from 'nodemailer'
import { SMTPServer as SMTPServerLib } from 'smtp-server'
import { MemoryStorage } from '@maildev/core'
import { SMTPServer } from '../server.js'
describe('relay STARTTLS', () => {
let mailDir: string
let storage: MemoryStorage
let source: SMTPServer
let upstream: SMTPServerLib
let upstreamPort: number
// `session.secure` of every message the upstream server received
let deliveries: boolean[]
beforeEach(async () => {
mailDir = await mkdtemp(join(tmpdir(), 'maildev-relay-tls-'))
storage = new MemoryStorage()
await storage.initialize()
deliveries = []
// A plain-port relay that advertises STARTTLS, like a submission server on 587.
upstream = new SMTPServerLib({
authOptional: true,
logger: false,
onData(stream, session, callback) {
deliveries.push(session.secure)
stream.on('end', () => callback())
stream.resume()
},
})
await new Promise<void>((resolve) => upstream.listen(0, '127.0.0.1', () => resolve()))
upstreamPort = (upstream.server.address() as AddressInfo).port
source = new SMTPServer({ storage, mailDir, port: 0, host: '127.0.0.1' })
await source.start()
})
afterEach(async () => {
await source.stop()
await new Promise<void>((resolve) => upstream.close(() => resolve()))
await rm(mailDir, { recursive: true, force: true })
})
const send = () => {
const transport = nodemailer.createTransport({
host: '127.0.0.1',
port: source.getPort(),
secure: false,
})
return transport.sendMail({
from: 'a@example.com',
to: 'b@example.com',
subject: 'relay me',
text: 'hello',
})
}
it('upgrades the relay connection with STARTTLS when the server offers it', async () => {
source.setupRelay({ host: '127.0.0.1', port: upstreamPort, secure: false })
source.setAutoRelay({ enabled: true })
await send()
expect(deliveries).toEqual([true])
})
it('stays on cleartext with ignoreTLS even when the server offers STARTTLS', async () => {
source.setupRelay({ host: '127.0.0.1', port: upstreamPort, secure: false, ignoreTLS: true })
source.setAutoRelay({ enabled: true })
await send()
expect(deliveries).toEqual([false])
})
})
+2
View File
@@ -36,6 +36,7 @@ export class RelayClient {
host: config.host || 'localhost',
port: config.port || (config.secure ? 465 : 25),
secure: config.secure || false,
ignoreTLS: config.ignoreTLS || false,
auth: config.auth,
}
this.logger = createLogger(logger)
@@ -152,6 +153,7 @@ export class RelayClient {
port: this.config.port,
host: this.config.host,
secure: this.config.secure,
ignoreTLS: this.config.ignoreTLS,
tls: { rejectUnauthorized: false },
debug: false,
})
+2
View File
@@ -86,6 +86,8 @@ export interface RelayConfig {
port: number
/** Enable TLS/SSL */
secure?: boolean
/** Never upgrade a non-secure connection with STARTTLS, even if the server offers it */
ignoreTLS?: boolean
/** Authentication credentials */
auth?: SMTPAuth | undefined
}