Billionmail/BillionMail · error
SMTP STARTTLS: %w
Error message
SMTP STARTTLS: %w
What it means
This error is returned when client.StartTLS fails after a successful plain dial on port 587. The server either rejected the STARTTLS command (e.g. 454 TLS not available), does not support it, or the subsequent TLS handshake failed. The client is closed before returning so no socket leaks.
Source
Thrown at core/internal/service/mail_service/sending.go:249
return nil
}
// connectPlain establishes a plain SMTP connection
func (e *EmailSender) connectPlain() error {
client, err := smtp.Dial(net.JoinHostPort(e.Host, e.Port))
if err != nil {
return fmt.Errorf("SMTP dial: %w", err)
}
// Check if STARTTLS is needed
if e.Port == "587" {
if err = client.StartTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
ServerName: e.SNI,
}); err != nil {
client.Close()
return fmt.Errorf("SMTP STARTTLS: %w", err)
}
}
var auth smtp.Auth
if e.Port == "25" {
auth = &customAuth{e.UserName, e.Password}
} else {
auth = smtp.PlainAuth("", e.UserName, e.Password, e.Host)
}
if err = client.Auth(auth); err != nil {
client.Close()
return fmt.Errorf("SMTP auth: %w", err)
}
e.client = client
return nilView on GitHub (pinned to fc36c76c05)
Solutions
- Check the server advertises STARTTLS in EHLO (swaks or openssl s_client -starttls smtp -connect host:587).
- Set e.SNI to the SMTP hostname so the TLS handshake presents the correct ServerName; an empty SNI is a frequent cause.
- If STARTTLS is genuinely unavailable on the relay, use port 25 without STARTTLS or port 465 implicit TLS instead.
- Check the wrapped alert code: handshake failures with old servers require server upgrades since MinVersion is fixed at TLS 1.2.
Example fix
// before
if err = client.StartTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
ServerName: e.SNI,
}); err != nil {
client.Close()
return fmt.Errorf("SMTP STARTTLS: %w", err)
}
// after
serverName := e.SNI
if serverName == "" {
serverName = e.Host // fall back so SNI is always sent
}
if err = client.StartTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
ServerName: serverName,
}); err != nil {
client.Close()
return fmt.Errorf("SMTP STARTTLS to %s: %w", serverName, err)
} Defensive patterns
Strategy: validation
Validate before calling
func supportsSTARTTLS(host, port, serverName string) error {
conn, err := smtp.Dial(net.JoinHostPort(host, port))
if err != nil {
return err
}
defer conn.Close()
if ok, _ := conn.Extension("STARTTLS"); !ok {
return fmt.Errorf("%s:%s does not advertise STARTTLS", host, port)
}
return conn.StartTLS(&tls.Config{MinVersion: tls.VersionTLS12, ServerName: serverName})
} Type guard
func isStartTLSErr(err error) bool {
var protoErr *textproto.Error
return errors.As(err, &protoErr) || errors.Is(err, tls.RecordHeaderError{}) || strings.Contains(err.Error(), "STARTTLS")
} Try / catch
if err := sender.Send(msg, rcpts); err != nil {
if strings.Contains(err.Error(), "SMTP STARTTLS") {
// fall back to port 465 implicit TLS or plaintext port 25 relay
log.Printf("STARTTLS failed on %s:%s, switching transport", host, port)
}
} Prevention
- Always set SNI/ServerName for STARTTLS — empty SNI is a common handshake breaker.
- Verify the EHLO capabilities include STARTTLS before selecting port 587.
- Keep server TLS versions >= 1.2 to match the client's MinVersion.
When it happens
Trigger: connectPlain dials a port-587 server and calls client.StartTLS with MinVersion TLS 1.2 and ServerName e.SNI; the command fails or the handshake fails because STARTTLS is unsupported, e.SNI is empty/mismatched, or the server cannot negotiate TLS 1.2+.
Common situations: Configuring port 587 on a relay that only offers plaintext or already-TLS service; e.SNI left empty so the handshake sends wrong/no SNI and strict servers abort; old server limited to TLS 1.0/1.1 while MinVersion is fixed at 1.2; middleboxes stripping STARTTLS capabilities.
Related errors
- TLS dial: %w
- new SMTP client: %w
- SMTP auth: %w
- SMTP server is required
- SMTP port must be between 1 and 65535
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/a98a48b558ed31fd.
Report an issue: GitHub.