ory/kratos · error

courier: email recipient is not a valid email address

Error message

courier: email recipient is not a valid email address

What it means

QueueEmail (courier/smtp.go:116) validates the email recipient before queuing: it must parse as an RFC 5322 address or match the library's IsEmailAddress check. The error message deliberately excludes the address because it is identity PII. A malformed recipient on a mail courier message aborts queuing and returns uuid.Nil.

Solutions

  1. Fix the identity's email trait (via API: PUT /admin/identities/{id}) to a valid address and retry the flow
  2. Validate/normalize emails at identity-creation time (schema `format: email` on the trait)
  3. Check for leading/trailing whitespace or hidden characters in the stored address and trim them
  4. If the address is intentionally nonstandard, use an RFC 5322 compliant form (e.g. `"Name" <a@b.com>` is fine; bare invalid strings are not)

Example fix

// before
{"traits": {"email": "jane.doe@"}}
// after
{"traits": {"email": "jane.doe@example.com"}}
Defensive patterns

Strategy: validation

Validate before calling

func validEmail(s string) bool {
  _, err := net/mail.ParseAddress(strings.TrimSpace(s))
  return err == nil
}
// guard before enqueueing courier email
if !validEmail(identity.Traits["email"]) { /* fix or reject */ }

Type guard

func isEmailAddress(s string) bool {
  s = strings.TrimSpace(s)
  if s == "" { return false }
  _, err := net/mail.ParseAddress(s)
  return err == nil && strings.Contains(s, "@")
}

Prevention

When it happens

Trigger: Enqueueing a courier email template message whose recipient field is an empty string, a username without domain, contains spaces/invalid characters, or is otherwise not a valid email; identity verifications or recovery flows with a corrupted identity's contact address.

Common situations: Identities imported via CSV/API with `email` traits that are not real addresses (e.g. 'jane', 'jane@@x', trailing whitespace/newlines); upstream systems writing invalid values into the identity traits.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at courier/smtp.go:116

	case "smtps":
		dialer.TLSConfig = tlsConfig
		dialer.SSL = true
	}

	return &SMTPClient{
		Dialer: dialer,
	}, nil
}

func (c *courier) QueueEmail(ctx context.Context, t EmailTemplate) (uuid.UUID, error) {
	recipient, err := t.EmailRecipient()
	if err != nil {
		return uuid.Nil, errors.WithStack(err)
	}
	// The RFC 5322 fallback keeps previously-valid addresses working; the
	// address is not included in the error because it is identity PII.
	if _, err := stdmail.ParseAddress(recipient); err != nil && !x.IsEmailAddress(recipient) {
		return uuid.Nil, errors.New("courier: email recipient is not a valid email address")
	}

	subject, err := t.EmailSubject(ctx)
	if err != nil {
		return uuid.Nil, errors.WithStack(err)
	}

	bodyPlaintext, err := t.EmailBodyPlaintext(ctx)
	if err != nil {
		return uuid.Nil, errors.WithStack(err)
	}

	templateData, err := json.Marshal(t)
	if err != nil {
		return uuid.Nil, errors.WithStack(err)
	}

	requestHeaders := []byte(`{}`)

View on GitHub (pinned to b86338da04)