Profile and account lifecycle Required
Keep the old email until the new one is confirmed
Store a new email address as pending until its owner confirms it. Keep sign-in, resets and notices on the old address until then.
How to do it
Proof and notices follow ask again before changing how someone logs in. Store the pending address, its key and a confirmation token that expires in 24 hours. Build it as sign-in links says, but it never signs anyone in, so the longer lifetime is safe. Check that no other account holds the new key when confirming, not only when asking. Give the old address's notice an undo link that works for 7 days. Then update the address with every service that holds it, such as the payment and email providers.
Example
export async function confirmEmailChange(db, token) {
const change = await db.emailChanges.findUnexpiredByHash(sha256(token));
if (!change) throw new GoneError('This link has expired. Ask for a new one in settings.');
await db.tx(async tx => {
if (await tx.users.existsWithEmailKey(change.newEmailKey)) throw new ConflictError('That address is already in use.');
await tx.users.setEmail(change.userId, change.newEmail, change.newEmailKey);
await tx.emailChanges.markConfirmed(change.id);
});
await syncEmailToProviders(change.userId);
}
def confirm_email_change(db, token: str) -> None:
change = db.email_changes.find_unexpired_by_hash(sha256(token))
if change is None:
raise Gone("This link has expired. Ask for a new one in settings.")
with db.transaction():
if db.users.exists_with_email_key(change.new_email_key):
raise Conflict("That address is already in use.")
db.users.set_email(change.user_id, change.new_email, change.new_email_key)
db.email_changes.mark_confirmed(change.id)
sync_email_to_providers(change.user_id)
How to check
Ask for a change and leave it unconfirmed. A password reset still mails the old address. Confirm, and resets mail the new one. The undo link in the old address's notice restores it.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/profile-and-account-lifecycle/email-change.