finalizeCreateUser() (run when a checker approves a CREATE_USER request queued in m_user_pending_request) saved AppUser.fromJson()'s result straight to m_appuser via appUserRepository.saveAndFlush() with no encoding step for the password AppUser.fromJson() auto-generates when the maker ticks "send password to email" -- AppUser's constructor stores whatever string it is given verbatim, so that random password was landing in m_appuser.password as plain text. Only the maker-chosen-password case (request.getPasswordHash() != null) was ever hashed, because that happens earlier, at submitCreateUser() time. It also never called the email service at all, in either case. The live create-user flow (JpaUserDomainService.create()) captures the unencoded password, encodes it, saves, then emails the welcome message -- finalizeCreateUser() duplicates everything up to the save but stops there, so a user created through this maker-checker flow never received their credentials regardless of what sendPasswordToEmail said in the submitted payload. Fix: capture the auto-generated plaintext right after AppUser.fromJson(), encode it with this class's own PasswordEncoder bean (already used the same way for the maker-chosen-password branch, consistent within the file) before saving, and call PlatformEmailService.sendToUserAccount() afterwards when sendPasswordToEmail was true and no maker-chosen password overrides it. A maker-chosen password is unrecoverable by approval time (submitCreateUser() redacts it to "[PROTECTED]" immediately), so that case still cannot be emailed, and isn't -- there is nothing wrong to fix there. Found by manually submitting a CREATE_USER request locally and reading both AppUser.fromJson() and the approval path end to end; no existing approved user was re-created to verify in place, since doing so would need resetting a real password. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>