Billionmail/BillionMail · error
reconnect failed: %w
Error message
reconnect failed: %w
What it means
After doSend failed (likely a stale connection), Send closes the client, nulls the connection, and retries Connect() once; this error wraps that reconnect failure. It means the fresh full connect (dial/TLS/auth) also failed, so the original send error is superseded by the reconnect error. Check the wrapped chain for the underlying cause.
Source
Thrown at core/internal/service/mail_service/sending.go:340
e.mutex.Lock()
}
// Try to send the message, with reconnect on failure
err := e.doSend(message, recipients)
if err != nil {
// Connection might be stale, try to reconnect once
g.Log().Debug(context.Background(), "SMTP send failed, attempting reconnection")
// Clean up
e.client.Close()
e.connected = false
e.client = nil
// Try to reconnect
e.mutex.Unlock()
if err := e.Connect(); err != nil {
e.mutex.Lock()
return fmt.Errorf("reconnect failed: %w", err)
}
e.mutex.Lock()
// Try sending again after reconnect
return e.doSend(message, recipients)
}
return nil
}
// doSend performs the actual message sending
func (e *EmailSender) doSend(message Message, recipients []string) error {
// Add default headers if not present
if message.Headers == nil {
message.Headers = make(map[string]string)
}
// Add Message-ID if not already setView on GitHub (pinned to fc36c76c05)
Solutions
- Unwrap to the underlying Connect error (dial vs TLS vs auth) and fix accordingly — do not assume the original doSend error was a network issue.
- Add backoff between the doSend failure and the reconnect attempt to ride out transient outages and avoid rate-limit loops.
- Verify credentials are still valid if the reconnect fails specifically at the AUTH stage.
- Consider a connection-health check (NOOP) before each send instead of relying on failure-then-reconnect.
Example fix
// before
e.mutex.Unlock()
if err := e.Connect(); err != nil {
e.mutex.Lock()
return fmt.Errorf("reconnect failed: %w", err)
}
e.mutex.Lock()
// after
e.mutex.Unlock()
time.Sleep(time.Second) // brief backoff before reconnect
var connErr error
for attempt := 0; attempt < 3; attempt++ {
if connErr = e.Connect(); connErr == nil {
break
}
time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
}
e.mutex.Lock()
if connErr != nil {
return fmt.Errorf("reconnect failed: %w", connErr)
} Defensive patterns
Strategy: retry
Validate before calling
func sendWithGuard(sender *EmailSender, msg Message, rcpts []string) error {
err := sender.Send(msg, rcpts)
if err != nil && strings.Contains(err.Error(), "reconnect failed") {
time.Sleep(5 * time.Second) // server likely down: wait before giving up
err = sender.Send(msg, rcpts)
}
return err
} Type guard
func isReconnectErr(err error) bool {
return strings.Contains(err.Error(), "reconnect failed")
} Try / catch
if err := sender.Send(msg, rcpts); err != nil {
if isReconnectErr(err) {
// schedule the message for a later retry queue rather than failing hard
enqueueForRetry(msg, rcpts, backoff=1*time.Minute)
return nil
}
return err
} Prevention
- Use bounded exponential backoff for reconnects; a single immediate retry often hits the same outage.
- Check server health (port probe) before attempting reconnect to distinguish outage from config errors.
- Persist failed messages to a retry queue instead of dropping them on reconnect failure.
When it happens
Trigger: Send() on an existing connection whose doSend errored (server closed connection, timeout, reset), then the automatic re-connect also fails — network outage, server down, or credentials no longer accepted.
Common situations: SMTP server restarted or idle-killed the connection and is temporarily refusing new ones; a network blip spanning both the send failure and reconnect; password rotated between the original connect and reconnect; server rate-limiting repeated connections from the same IP.
Related errors
- failed to connect: %w
- failed to connect to SMTP server: %v
- redis connection test failed after 10 attempts
- 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/de36e618a749d9ab.
Report an issue: GitHub.