n8n-io/n8n · error · Error

Cannot save user <${this.email}>: Provided email is invalid

Error message

Cannot save user <${this.email}>: Provided email is invalid

What it means

Thrown from User entity's @BeforeInsert/@BeforeUpdate hook (preUpsertHook) when the email field, after lowercasing, fails isValidEmail(). Fires on any persistence path (TypeORM insert/update) where email is set — including empty strings, since the hook explicitly validates non-null/undefined values. Bare `new Error(...)`, not an n8n error class.

Source

Thrown at packages/@n8n/db/src/entities/user.ts:91

	@OneToMany('SharedCredentials', 'user')
	sharedCredentials: SharedCredentials[];

	@OneToMany('ProjectRelation', 'user')
	projectRelations: ProjectRelation[];

	@Column({ type: Boolean, default: false })
	disabled: boolean;

	@BeforeInsert()
	@BeforeUpdate()
	preUpsertHook(): void {
		this.email = this.email?.toLowerCase() ?? null;

		// Validate email if present (including empty strings)
		if (this.email !== null && this.email !== undefined) {
			const result = isValidEmail(this.email);
			if (!result) {
				throw new Error(`Cannot save user <${this.email}>: Provided email is invalid`);
			}
		}
	}

	@Column({ type: Boolean, default: false })
	mfaEnabled: boolean;

	@Column({ type: String, nullable: true })
	mfaSecret?: string | null;

	@Column({ type: 'simple-array', default: '' })
	mfaRecoveryCodes: string[];

	@Column({ type: 'date', nullable: true })
	lastActiveAt?: Date | null;

	/**
	 * Whether the user is pending setup completion.

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Run isValidEmail() (from @n8n/db or n8n-workflow email utils) on the value BEFORE attempting to save the user.
  2. Normalize upstream: if email comes from LDAP/SAML/SCIM, reject or default the record when mail is absent/invalid.
  3. Treat empty email explicitly — decide between null (allowed) and an actual address; never pass '' for a required email.
  4. For bulk imports, validate the entire batch first and report offending rows rather than failing row-by-row.

Example fix

// before
await userRepo.save({ email: input.email });

// after
if (input.email !== null && input.email !== undefined && !isValidEmail(input.email)) {
  throw new UserError(`Refusing to save user: email '${input.email}' is invalid`);
}
await userRepo.save({ email: input.email?.toLowerCase() ?? null });
Defensive patterns

Strategy: validation

Validate before calling

import { isValidEmail } from '@n8n/db'; // or wherever exposed
function assertEmail(email: string | null | undefined) {
  if (email !== null && email !== undefined && !isValidEmail(email)) {
    throw new Error(`Invalid email: '${email}'`);
  }
}

Type guard

function isValidUserEmail(email: unknown): email is string {
  return typeof email === 'string' && isValidEmail(email);
}

Try / catch

try {
  await userRepo.save(user);
} catch (err) {
  if (err instanceof Error && /Cannot save user .* Provided email is invalid/.test(err.message)) {
    // reject the input, surface a field-level error to the user
  } else throw err;
}

Prevention

When it happens

Trigger: Creating or updating a User with a malformed email (missing @, invalid TLD, control chars), an empty string '', or a value that fails the project's isValidEmail regex/validation. Triggered via repositories, services, or any direct TypeORM save on the User entity.

Common situations: SSO/import scripts feeding dirty data; a signup form bypassing client-side validation; an LDAP/SAML sync mapping an empty/missing mail attribute to email; migrations seeding users with placeholder addresses like 'user@'; tests using 'invalid' as an email.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/eb09c0817eedfdff. Report an issue: GitHub.