theonedev/onedev · error · ExplicitException

At least one email address should be present for a user

Error message

At least one email address should be present for a user

What it means

deleteEmailAddress requires every user to retain at least one email address, so deleting the user's last remaining address throws ExplicitException("At least one email address should be present for a user"). This invariant keeps the account reachable for notifications, commits attribution and login-related flows.

Source

Thrown at server-core/src/main/java/io/onedev/server/rest/resource/EmailAddressResource.java:146

		emailAddressService.sendVerificationEmail(emailAddress);
		
		return emailAddressId;
	}
	
	@Api(order=300)
	@Path("/{emailAddressId}")
	@DELETE
	public Response deleteEmailAddress(@PathParam("emailAddressId") Long emailAddressId) {
		var emailAddress = emailAddressService.load(emailAddressId);
		if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(getAuthUser())) 
			throw new UnauthorizedException();
		
		if (emailAddress.isPrimary() && emailAddress.getOwner().getPassword() == null) {
			throw new ExplicitException("Cannot delete primary email address of "
					+ "externally authenticated user");
		}
		if (emailAddress.getOwner().getEmailAddresses().size() == 1)
			throw new ExplicitException("At least one email address should be present for a user");
		emailAddressService.delete(emailAddress);

		if (!getAuthUser().equals(emailAddress.getOwner())) 
			auditService.audit(null, "deleted email address \"" + emailAddress.getValue() + "\" from account \"" + emailAddress.getOwner().getName() + "\" via RESTful API", null, null);

		return Response.ok().build();
	}
	
}

View on GitHub (pinned to d44925c47c)

Solutions

  1. Add a new email address (POST /~api/emailAddresses) before deleting the last one.
  2. Skip addresses that are the user's last one in your cleanup script (check count > 1 first).
  3. If the whole account should go away, deactivate/delete the user account instead of its addresses.

Example fix

// before: delete blindly
addresses.forEach(a -> client.deleteEmailAddresses(a.getId()));
// after: keep at least one
if (addresses.size() > 1)
    addresses.forEach(a -> client.deleteEmailAddresses(a.getId()));
Defensive patterns

Strategy: validation

Validate before calling

// only delete when the user keeps at least one address
var remaining = owner.getEmailAddresses().size();
if (remaining <= 1) throw new IllegalStateException("refusing to delete last email address of " + owner.getName());

Try / catch

try { client.deleteEmailAddress(id); }
catch (ExplicitException e) { log.warn("Cannot remove last email address; add a replacement first"); }

Prevention

When it happens

Trigger: DELETE /~api/emailAddresses/{id} when emailAddress.getOwner().getEmailAddresses().size() == 1, i.e. the target is the user's only email address.

Common situations: Scripts iterating all addresses of a user and deleting each without checking remaining count; cleanup jobs that accidentally remove the sole address of a new account.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/37c8212ca44ff117. Report an issue: GitHub.