ory/kratos · error

unable to dispatch mail delivery because upstream server…

Error message

unable to dispatch mail delivery because upstream server replied with status code %d

What it means

The HTTP courier channel finished POSTing a message to the configured upstream URL and received a response with a status outside the 2xx range. The upstream delivery endpoint rejected the request; the status code is included in the error.

Solutions

  1. Check the upstream server's logs for why it returned the non-success status
  2. Verify the request URL, auth and payload format expected by the HTTP channel configuration
  3. If the failure was transient (e.g. 502/503), let the courier retry the dispatch
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at courier/http_channel.go:121 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07). Data as JSON: /api/errors/8a4c54dfa8e05463. Report an issue: GitHub.

Appendix: source

Thrown at courier/http_channel.go:121

	}
	// Close the original body, not the NopCloser below: closing it returns
	// the connection to the pool and ends the otelhttp client span.
	defer func(body io.ReadCloser) { _ = body.Close() }(res.Body)
	res.Body = io.NopCloser(io.LimitReader(res.Body, 1024))

	logger := c.d.Logger().
		WithField("http_server", c.requestConfig.URL).
		WithField("message_id", msg.ID).
		WithField("message_nid", msg.NID).
		WithField("message_type", msg.Type).
		WithField("message_template_type", msg.TemplateType).
		WithField("message_subject", msg.Subject)

	if res.StatusCode >= 200 && res.StatusCode < 300 {
		logger.Debug("Courier sent out mailer.")
		return nil
	}

	err = errors.Errorf(
		"unable to dispatch mail delivery because upstream server replied with status code %d",
		res.StatusCode,
	)

	body, _ := io.ReadAll(res.Body)
	logger.
		WithError(err).
		WithField("http_response_body", string(body)).
		Error("sending mail via HTTP failed.")

	return errors.WithStack(err)
}

func (c *httpChannel) tryPopulateHTMLBody(ctx context.Context, tmpl Template, td *httpDataModel) {
	if emailTmpl, ok := tmpl.(EmailTemplate); ok {
		// Only get the HTML body from the template; plaintext body comes from msg.Body
		// to maintain backward compatibility with existing behavior

View on GitHub (pinned to b86338da04)