semaphoreui/semaphore · error

unexpected server challenge

Error message

unexpected server challenge

What it means

With authMethod "PLAIN", the client sends username and password in a single initial response, so no further server challenge is expected. If the server sends another challenge during Next, the state machine cannot continue and this error is returned. It usually indicates the server does not actually support PLAIN as advertised.

Solutions

  1. Force the LOGIN mechanism (remove PLAIN from the server's advertised mechanisms or configure the client to prefer LOGIN).
  2. Fix or replace the SMTP server/proxy so it honors RFC 4616 PLAIN semantics (single message, no follow-up challenge).
  3. Capture the SMTP session and verify which AUTH mechanisms the server actually advertises after STARTTLS.

Example fix

// before
// server advertises ["PLAIN","LOGIN"] but only implements LOGIN
// after (client side preference)
if !slices.Contains(server.Auth, "PLAIN") || serverBrokenPLAIN {
    a.authMethod = "LOGIN" // use LOGIN flow for challenge/response
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the server truly supports PLAIN before selecting it
if !slices.Contains(serverInfo.Auth, "PLAIN") {
    return errors.New("server does not advertise PLAIN; use LOGIN instead")
}

Try / catch

_, _, err := auth.Next(fromServer, true)
if err != nil && errors.Is(err, errUnexpectedChallenge) || err.Error() == "unexpected server challenge" {
    // fall back to LOGIN mechanism and restart AUTH
    return restartAuthWithLogin(client, username, password)
}

Prevention

When it happens

Trigger: Server advertises PLAIN in its AUTH mechanisms (so plainAuth is selected) but then issues an additional challenge instead of accepting the one-shot response - misbehaving or misconfigured SMTP server.

Common situations: Relay advertising PLAIN but implemented as LOGIN-only; middleware/proxy mangling the AUTH exchange; server state inconsistent after STARTTLS.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/b55996102c31ffca. Report an issue: GitHub.

Appendix: source

Thrown at util/mailer/auth.go:55

	}
	if !slices.Contains(server.Auth, "PLAIN") {
		a.authMethod = "LOGIN"
		return a.authMethod, nil, nil
	} else {
		a.authMethod = "PLAIN"
		resp := []byte("\x00" + a.username + "\x00" + a.password)
		return a.authMethod, resp, nil
	}
}

func (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if !more {
		return nil, nil
	}

	if a.authMethod == "PLAIN" {
		// We've already sent everything.
		return nil, errors.New("unexpected server challenge")
	}

	switch {
	case bytes.Equal(fromServer, []byte("Username:")):
		return []byte(a.username), nil
	case bytes.Equal(fromServer, []byte("Password:")):
		return []byte(a.password), nil
	default:
		return nil, fmt.Errorf("unexpected server challenge: %s", fromServer)
	}
}

View on GitHub (pinned to 1774ccb71a)