devise: Initial confirmation email not being sent

Per the documentation “Confirmation instructions are sent to the user email after creating a record and when manually requested by a new confirmation instruction request.”. However, in my deployment I’m currently not receiving the confirmation emails on initial user creation. The manual request for a confirmation is working perfectly.

I am unsure if it is some side effect of a configuration that I have. I am able to resolve the issue by adding the following code change to send_confirmation_notification? to consider the unconfirmed_email as the email value is initially blank:

# lib/devise/models/confirmable.rb
def send_confirmation_notification?
    confirmation_required? && !@skip_confirmation_notification && (!self.email.blank? || !self.unconfirmed_email.blank?)
end

I’m running with Devise 3.1.1 and have the following config settings related to confirmations:

# config/devise.rb
config.allow_unconfirmed_access_for = 0.days
config.confirm_within = 3.days
config.reconfirmable = true

Curious if anybody else is seeing this issue or comments / thoughts on the implications of the code change I have made?

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Reactions: 2
  • Comments: 25 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I’m having this issue too. And it’s apparently because of DeviseTokenAuth, for me:

class User < ActiveRecord::Base
  devise :registerable, :confirmable, :database_authenticatable # (minimal set)

  include DeviseTokenAuth::Concerns::User
end

Removing the DeviseTokenAuth concerns solves this issue, as does placing it above the devise settings. However, I’ve currently not tested whether the tokens still work…

My fixed User class:

class User < ActiveRecord::Base
  include DeviseTokenAuth::Concerns::User

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable
end

For now, I have a hugly workaround: In my User model, I added this:

class User < ActiveRecord::Base
  devise :confirmable

  after_create: force_mail_confirmation

  def force_mail_confirmation
    self.send_confirmation_instructions
  end
end

Works like a charm… except that I suspect a skip_confirmation call would work anymore 😕

Any help is more than welcome

Same issue with three solutions. I discovered it using devise_invitable and when sending a new invite I got the error:

An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.

After tracing through devise_invitable and devise, I determined it happened during if save(:validate => false) in the class instance method invite! in lib/devise_invitable/model.rb. After checking the callbacks I focused on the devise before_update callback postpone_email_change_until_confirmation_and_regenerate_confirmation_token.

By itself, this callback isn’t an issue even with devise_invitable. As described by others, the problem occurs when another save happens during creation (typically within after_create, after_save, or after_commit). This will trigger the update flow and callbacks which leads to postpone_email_change_until_confirmation_and_regenerate_confirmation_token.

        def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
          @reconfirmation_required = true
          self.unconfirmed_email = self.email
          self.email = self.email_was
          self.confirmation_token = nil
          generate_confirmation_token
        end

At this point, self.email = self.email_was sets email to "". self.email_was is part of ActiveModel::Dirty. Because the same model instance is being used, self.email_was returns "" - because email did not have a previous value when the record was created. Devise is assuming that the change in email is due to an update and not because of the creation. This assumption plays out here as it relies upon email_changed? (also from ActiveModel::Dirty) and if this new email should be reconfirmed.

        def postpone_email_change?
          postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
          @bypass_confirmation_postpone = false
          postpone
        end

The following user model code produces the issue. In this example, sign_in_count will be set 1 only if 0 and then saves.

class User < ActiveRecord::Base
...

  after_save :reveal_issue

  def reveal_issue
    if self.sign_in_count == 0
      self.sign_in_count == 1
      self.save
    end
  end

You can then verify it in the console.

Create user and see that email is present in the user instance: user = User.new(email: "sim@ple.ton", password:"simpleton", password_confirmation: "simpleton")

Save user: user.save

Check user and email is now "": user

Check last user stored and email is present: User.last

Obviously a trivial example, but I found the issue because I had an after_save that was updating the username to a default that had the user id as part of the username. This could only happen after the record was saved so I could use the assigned id.


The following solutions have been tested only so far that the model instance email is not set to "".

Solution 1

Use skip_reconfirmation! to bypass postpone_email_change_until_confirmation_and_regenerate_confirmation_token during this update. I prefer this and it works well.

class User < ActiveRecord::Base
...

  after_save :reveal_issue

  def reveal_issue
    if self.sign_in_count == 0
      self.skip_reconfirmation!
      self.sign_in_count == 1
      self.save
    end
  end

Solution 2

Make the updates after creation only if confirmation has occurred. This only works for after_save and after_commit. after_create only fires once upon creation so it would never execute the code.

class User < ActiveRecord::Base
...

  after_save :reveal_issue

  def reveal_issue
    if self.confirmed? && self.sign_in_count == 0
      self.sign_in_count == 1
      self.save
    end
  end

Solution 3

Force a record reload before making updates. This fulfills the devise assumption concerning the state of the instance though it is another database hit.

class User < ActiveRecord::Base
...

  after_save :reveal_issue

  def reveal_issue
    if self.sign_in_count == 0
      self.reload
      self.sign_in_count == 1
      self.save
    end
  end

Same here, @lkol’s “config.reconfirmable = false” did resolve this, wondering what the implication might be?

I was experiencing the same problem and was only able to resolve it by setting config.reconfirmable = falsefor now. If the field is set as true, the original confirmation email won’t be sent.