Skip to content

Send confirmation email to pending email with appropriate copy#1447

Open
maebeale wants to merge 5 commits intomainfrom
maebeale/fix-change-pw-policy
Open

Send confirmation email to pending email with appropriate copy#1447
maebeale wants to merge 5 commits intomainfrom
maebeale/fix-change-pw-policy

Conversation

@maebeale
Copy link
Copy Markdown
Collaborator

@maebeale maebeale commented Mar 30, 2026

Summary

  • Confirmation emails now send to the pending email address (not the current one) when a user changes their email
  • Email copy distinguishes between new account confirmation and email change confirmation
  • Fixed test failures from factory triggering on-create confirmation with Devise 5

Test plan

  • Mailer specs pass
  • Model specs pass
  • Service specs pass

🤖 Generated with Claude Code

maebeale and others added 5 commits March 30, 2026 12:55
Override Devise's send_confirmation_instructions on User to always
route to unconfirmed_email when present, fixing flows where the
confirmation was sent to the old email instead of the new one.

Branch the confirmation email subject, HTML, and text templates:
- Welcome invite (new users): unchanged
- Reconfirmation (email change): new subject, CTA, and copy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add User model spec for send_confirmation_instructions override
verifying it routes to unconfirmed_email when present.

Update ProcessEmailChange and ProcessEmailManualConfirm specs to
assert the DeviseMailer receives the correct `to:` address rather
than just checking that send_confirmation_instructions was called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensure DeviseMailer stub is set up after user factory creation so
the on-create confirmation email doesn't inflate the mock count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Devise 5.0.3 calls deliver_now instead of deliver_later, so mock mail
doubles need to allow deliver_now. Also guard the confirm_within
template reference since the app doesn't set a token expiry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
end
end

def authenticate_user!
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to override this method at all? The only thing unique I see in it is the ahoy stuff but from the ahoy docs:

Users

Ahoy automatically attaches the current_user to the visit. With [Devise](https://github.com/heartcombo/devise), it attaches the user even if they sign in after the visit starts.

With other authentication frameworks, add this to the end of your sign in method:

ahoy.authenticate(user)

Ahoy

I don't have the full context on ahoy though, just point out what I found.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're using it for more than just visits and normal "events" -- it's also for model lifecycle tracking incl w callbacks and i couldn't figure out a different way to make sure it always had current_user in those situations.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we prob should consider the papertrail gem or something like that instead, but it's a small enough app that this ahoy adaptation seemed worth it for all analytics data to end up in the same tables.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that ahoy needs the current user but I don't understand why we need ahoy.authenticate explicitly.

In a normal devise setup we would keep our before_action authenticate_user and that would use the devise method. The ahoy documents also say if you are using devise it automatically attaches the current_user to the visit.

It goes on to say for other frameworks you would call ahoy.authenticate.

Here is our overriding method

  def authenticate_user!
    return super if devise_controller?

    if user_signed_in?
      ahoy.authenticate(current_user)
    else
      redirect_to root_path
    end
  end

The only thing in it ahoy.authenticate. I guess I don't understand why we need to override it if devise and ahoy already work together. We also lose the devise redirect behave when we just redirect to the root_path.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i'm missing something regarding the ahoy flow, maybe pulling out the ahoy stuff to its own method would work instead of overriding authenticate_user

before_action :authenticate_user!
before_action :track_user_with_ahoy

def track_user_with_ahoy
  ahoy.authenticate(current_user) if user_signed_in?
end

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooo! love this suggestion!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought it needs to also guard for if devise_controller, bc we don't want to run the ahoy thing in that case

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def authenticate_user! by default goes to new_session_path, so we need that override to send them to root_path. still, separating the concerns is a solid idea.

@maebeale maebeale force-pushed the maebeale/fix-change-pw-policy branch from 443c8e8 to df63986 Compare March 31, 2026 00:13
@maebeale maebeale changed the title Fix ArgumentError when unauthenticated user hits authorize! Send confirmation email to pending email with appropriate copy Mar 31, 2026
@maebeale maebeale marked this pull request as ready for review March 31, 2026 00:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants