How can I resend a Devise confirmation email automatically, but only once, for users who have not confirmed?

1.7k Views Asked by At

I am using Rails with Devise confirmable. When users sign up Devise automatically sends them a confirmation email. If they haven't confirmed after three days, I would like to send them another confirmation email with a new token. If they still do not confirm after this second email, we should send them no more. Mapped out on a timeline:

  • Day 1: User signs up & receives confirmation email; does not confirm
  • Day 2: User does not confirm
  • Day 3: Send another email prompting them to confirm; does not confirm
  • Day 4: User does not confirm; no more emails sent
  • Day 5: User does not confirm; no more emails sent
  • Day 6: etc.

I'd rather not add a new column to my Users model that keeps track of how many times they've been sent a confirmation email since I think I can do it in a Worker that runs every day using the information we already have. However I'm struggling a bit with getting the logic right.

Here's what I have so far:

# workers/resend_confirmation_worker.rb

class ResendConfirmationWorker
  include Sidekiq::Worker
  sidekiq_options queue: :resend_confirmation, retry: false

  def perform
    users = User.where('confirmation_sent_at IS NOT NULL and
                        confirmed_at IS NULL')
    users.each do |user|
      return if user.created_at < Time.zone.now - 4.days || user.created_at > Time.zone.now - 3.days
      user.resend_confirmation!
  end
end

# config/clock.rb

module Clockwork
  every(1.day, 'Resend confirmation') { ResendConfirmationWorker.perform_async }
end

Appreciate any help or suggestions for improvement.

3

There are 3 best solutions below

0
On BEST ANSWER
d = 2.days.ago
users = User.where.not(confirmation_sent_at: nil)
            .where(confirmed_at: nil)
            .where(created_at: d.beginning_of_day..d.end_of_day)

users.find_each do |user|
  user.resend_confirmation!
end
0
On

If you are going to run the worker once a day, you could probably use something like:

User.where('
  confirmed_at IS NULL
  AND created_at >= ?
  AND created_at <= ?
', 2.days.ago.beginning_of_day, 2.days.ago.end_of_day)

If you run this on Wednesday, it fetches all unconfirmed users that are registered on Monday.

0
On

The answer posted by max is almost perfect however you need to use user.send_confirmation_instructions instead of resend_confirmation!, and if the worker ran more than once in a day by accident (e.g. due to a dyno restart), people could potentially get too many confirmation emails.

Now all I'm trying to figure out is why my tests aren't working.