cloudflare / cloudflare/workerd
🐛 Bug Report — Runtime APIs cloudflare:sockets - Unable to use startTls for SMTP
- Dominant language
- C++
- Stars
- 8.7k
- Forks
- 739
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 174
Description
Hey all, been having an issue getting startTls working with sockets. This issue has been going on a while now, so I'll break it down into a few scenarios. In all these examples, I am running the API from cloudflare workers, while the website is hosted with pages.
1. Tried sending an SMTP message to `smtp.protonmail.ch` on port 587 using startTls. Was weirdly unable to get it to work. For this use case, protonmail allowed communication on port 465 with `{ secureTransport: 'on' }` (TLS enabled). It worked beautifully, so I forgot about it and moved on.
2. This go around, using outlook `smtp-mail.outlook.com` which isn't playing nicely on port 465. Documentation requires explicit startTls on 587.
3. The reproducible example attached, using the https://ethereal.email/ service to communicate with. Same issues - no dice with startTls().
I'll preface this with the fact that I don't have a deep understanding of TCP or TLS. My best guess at the moment is that I simply have something misconfigured (DNS/certificate related most likely). But I'm reaching the point where I've tried seemingly every permutation of settings / DNS records / code with nothing to show for it.
Here's the sample code snippet which I'm calling from within a route on my worker:
```
export async function testEmail(env: Env) {
const encoder = new TextEncoder()
const decoder = new TextDecoder()
// Using service https://ethereal.email/ for a test SMTP server
const socket = connect({ hostname: env.SMTP_HOST, port: 587 }, { secureTransport: 'starttls' })
// Socket does not hang
await socket.opened
const writer = socket.writable.getWriter()
const reader = socket.readable.getReader()
async function writeAndRead(command: string, writer: any, reader: any) {
console.log(`Sending: ${command.trim()}`)
// In practice this is not needed, but with ethereal, I receive
// 421 smtp.ethereal.email You talk too soon without a delay
await new Promise((resolve) => {setTimeout(resolve, 500)})
await writer.write(encoder.encode(command))
const { value } = await reader.read()
const response = decoder.decode(value)
console.log(`Response: ${response.trim()}`)
return response
}
// On insecure socket, these have no issues.
await writeAndRead('EHLO domain.com\r\n', writer, reader)
await writeAndRead('STARTTLS\r\n', writer, reader)
/**
* Have tried moving these before and after startTls call
* Before startTls() => This WritableStream writer has been released
* After startTls() => This WritableStream is currently locked to a writer
*/
writer.releaseLock()
await writer.close()
const secureSocket = socket.startTls()
// This call hangs, albeit unsure if it's needed/valid to call.
// await secureSocket.opened
const secureWriter = secureSocket.writable.getWriter()
const secureReader = secureSocket.readable.getReader()
// Will error out (After)
await writeAndRead('EHLO domain.com\r\n', secureWriter, secureReader)
}
```
Finally, just to dig into the settings for DNS and cloudflare TLS:
Redacted DNS

Custom SSL/TLS set to Full (Strict) although tried with flexible & full
Always Use HTTPS: Enabled
Minimum TLS version: Currently 1.1, but outlook requires >=1.2. I bumped this up sequentially during testing and no luck.
TLS 1.3: Enabled
Automatic HTTPS Rewrites: Enabled
Authenticated Origin Pulls: Disabled
Again, unsure if this is entirely a bug, but I'm at wit's end. At the very least, I'd recommend a logging improvement to help track down the error 😅
Contributor guide
Assessment
This issue has not been assessed yet.