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
- Fix the identity's email trait (via API: PUT /admin/identities/{id}) to a valid address and retry the flow
- Validate/normalize emails at identity-creation time (schema `format: email` on the trait)
- Check for leading/trailing whitespace or hidden characters in the stored address and trim them
- 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
- Enforce `format: email` in the identity JSON schema so invalid emails are rejected at registration
- Sanitize/trim email traits when importing identities via API or CSV
- Periodically audit identities for invalid contact addresses using the admin API
- Remember the error hides the address (PII); check server logs/identity data to find the offender
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
- you must provide `secrets.pagination` for FIPS compliance
- the provided number is not a valid phone number
- identity schema rejected: invalid regex in pattern
- unknown courier channel type
- no courier channels configured for
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)