Hand the CONNECT Tunnel to Go’s SMTP Client
Use smtp.NewClient with an established tunnel and keep the SMTP hostname and STARTTLS handshake separate from proxy setup.
smtp.Dial opens a new connection to its address. After obtaining a connection through an HTTP CONNECT proxy, pass that existing stream to smtp.NewClient instead.
For an SMTP server that requires STARTTLS on its submission port, the handoff can be:
func smtpOverTunnel(conn net.Conn, mailHost string) (*smtp.Client, error) {
client, err := smtp.NewClient(conn, mailHost)
if err != nil {
conn.Close()
return nil, err
}
err = client.StartTLS(&tls.Config{
ServerName: mailHost,
MinVersion: tls.VersionTLS12,
})
if err != nil {
client.Close()
return nil, err
}
return client, nil
}
The packages are net, net/smtp and crypto/tls. The caller supplies a tunnel already connected to the intended SMTP host and port, with the proxy's CONNECT exchange completed. Merely opening TCP to the proxy is insufficient: an HTTP response is not an SMTP greeting.
mailHost is the SMTP server's hostname, without a port. Keep that identity for SMTP authentication and certificate verification. A TLS connection to an HTTPS proxy protects a different leg of the route.
The returned client is ready for the application's existing SMTP authentication and mail transaction. Close it when finished. Bound dialing, CONNECT and subsequent protocol operations with the application's deadline policy.
The proxy must allow CONNECT to the chosen mail port. Some restrict it to HTTPS destinations on port 443. For implicit TLS SMTP, establish the destination TLS session before calling NewClient instead of issuing STARTTLS.