usememos/memos · error

SMTP server does not support STARTTLS

Error message

SMTP server does not support STARTTLS

What it means

Returned by Client's STARTTLS path (sendWithTLS in internal/email/client.go) when UseTLS is true but the server's EHLO response does not advertise the STARTTLS extension. The client refuses to send credentials and message body over a plaintext connection, so it aborts before authenticating.

Source

Thrown at internal/email/client.go:103

	dialer := &net.Dialer{Timeout: smtpOperationTimeout}
	conn, err := dialer.Dial("tcp", serverAddr)
	if err != nil {
		return errors.Wrapf(err, "failed to connect to SMTP server: %s", serverAddr)
	}
	defer conn.Close()
	if err := conn.SetDeadline(time.Now().Add(smtpOperationTimeout)); err != nil {
		return errors.Wrap(err, "failed to set SMTP connection deadline")
	}

	client, err := smtp.NewClient(conn, c.config.SMTPHost)
	if err != nil {
		return errors.Wrap(err, "failed to create SMTP client")
	}
	defer client.Quit()

	if c.config.UseTLS {
		if ok, _ := client.Extension("STARTTLS"); !ok {
			return errors.New("SMTP server does not support STARTTLS")
		}
		if err := client.StartTLS(c.createTLSConfig()); err != nil {
			return errors.Wrap(err, "failed to start SMTP STARTTLS")
		}
	}

	return c.sendWithClient(client, auth, recipients, body)
}

// sendWithSSL sends email using SSL/TLS (port 465).
func (c *Client) sendWithSSL(auth smtp.Auth, recipients []string, body string) error {
	serverAddr := c.config.GetServerAddress()

	// Create TLS connection
	tlsConfig := c.createTLSConfig()
	dialer := &net.Dialer{Timeout: smtpOperationTimeout}
	conn, err := tls.DialWithDialer(dialer, "tcp", serverAddr, tlsConfig)
	if err != nil {

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. If the server is port 465 (implicit TLS), set UseSSL=true and UseTLS=false.
  2. If using port 587, confirm the server actually supports STARTTLS: openssl s_client -starttls smtp -connect host:587 or 'swaks -tls'.
  3. If the relay truly has no TLS, set UseTLS=false only over a trusted network (plaintext credentials otherwise).
  4. Check for middleboxes/proxies stripping EHLO extensions and use the TLS-capable port the provider documents.

Example fix

// before (465 with STARTTLS flag)
cfg := email.Config{SMTPHost: "smtp.example.com", SMTPPort: 465, UseTLS: true}

// after (465 uses implicit SSL)
cfg := email.Config{SMTPHost: "smtp.example.com", SMTPPort: 465, UseSSL: true}
Defensive patterns

Strategy: validation

Validate before calling

// Go — pick the TLS mode from the port before sending
if cfg.SMTPPort == 465 {
  cfg.UseSSL, cfg.UseTLS = true, false
}
if cfg.UseTLS {
  // cheap capability probe avoids a wasted dial+auth attempt
  conn, err := net.DialTimeout("tcp", cfg.GetServerAddress(), 5*time.Second)
  if err == nil {
    conn.Close()
  }
}

Try / catch

if err := client.Send(...); err != nil {
  if strings.Contains(err.Error(), "STARTTLS") {
    // either flip to UseSSL on 465, or pick a TLS-capable port; do NOT silently downgrade
    return errors.Wrap(err, "SMTP TLS mismatch: use UseSSL for port 465 or a STARTTLS-capable port")
  }
  return err
}

Prevention

When it happens

Trigger: UseTLS=true against a server that only offers plaintext (port 25 relay without TLS) or that requires implicit TLS on 465; the EHLO extension list genuinely lacks STARTTLS. Also appears when a firewall/misconfigured proxy strips the extension or when the wrong port is used (465 expects SSL, not STARTTLS).

Common situations: Using UseTLS with port 465 instead of UseSSL; self-hosted relay (e.g., a local null client) with TLS disabled; corporate smarthost that only supports TLS on a different port; copy-pasted config mixing the STARTTLS and SSL flags.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/4e3fd586a1cf1602. Report an issue: GitHub.