Search code examples
ruby-on-railsrubyemaildelayed-job

Rails 4 - How to send newsletter-like emails with delayed_job?


I want to send a summary of our new lists to our users every morning. What's the best approach to do that with Ruby On Rails 4, ActiveRecord (using SendGrid) and Delayed Job?

I am currently doing it this way:

In controller:

def yesterday_listings_for_users
    yesterday_listings = Listings.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1)
    if yesterday_listings.count > 0
      NotificationMailer.delay.yesterday_listings_for_users_notification
    end  
    render :nothing => true
  end

And then in the mailer:

  def yesterday_listings_for_users_notification
    @listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today-1)

    mail(to: '[email protected]', subject: "Latest Listings", from: '[email protected]')            
  end

With using a CRON job, this sends me the report every morning on my email address. I have a few hundreds of users in the database and I would like to send them this email as well.

How to do that? I am wondering about something like this:

  def yesterday_listings_for_users_notification
    @listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today-1)
    Users.all.each do |user|
      mail(to: user.email, subject: "Latest Listings", from: '[email protected]')          
    end
  end

However, is looping through hundreds of records in database and sending hundreds of emails in a delayed mailer method recommened (or appropriate)?

Is there a better way to do that?

Thank you in advance!


Solution

  • I usually prefer to use Sidekiq along with Sidetiq but if you want to use delayed_job I would advice you to use the whenever gem for simplicity.

    Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.

    1. Add gem 'whenever' to your gemfile
    2. run the command wheneverize . which will generate a file config/schedule.rb
    3. In your config/schedule.rb do the following.

      every 1.day, :at => '11:30 am' do
        runner "User.delay.send_daily_newsletter"
      end
      
    4. In your user.rb define the method send_daily_newsletter and use find_each instead of all.each (batches)

      def self.send_daily_newsletter
        listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1).select(:title).to_json
        User.select(:id, :email).find_each do |u|
          NotificationMailer.delay.send_daily_newsletter(u.email, listings)
        end
       end
      
    5. In your notification_mailer.rb define send_daily_newletter

      def send_daily_newsletter(user_email, listings)
        @listings = listings
        mail(to: user_email, subject: "Latest Listings", from: '[email protected]')
      end
      

    This way you will have one delayed job to get all users and send each email using a separate worker which is the most optimal way to do this task.

    Note: Do not forget to change the methods for listings in your view from, for example, listing.title to listing[:title] since we are passing the listings as json.

    If you do not want to pass them as json every time to every delayed task just cache the listings in Rails.cache and clear it after you finish sending.

    EDIT:

    If you would like to use the cache method since you ran into a problem in the delayed_job gem, edit your send_daily_newsletter method in your mailer. (That's is why I would go to redis-based Sidekiq rather than mysql-based delayed_job.

        def send_daily_newsletter(user_email)
          @listings = Rails.cache.fetch('today_listings') { Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1) }
          mail(to: user_email, subject: "Latest Listings", from: '[email protected]')
        end
    

    and in your user.rb

         def self.send_daily_newsletter
          User.select(:id, :email).find_each do |u|
            NotificationMailer.delay.send_daily_newsletter(u.email)
          end
          Rails.cache.clear('today_listings')
         end
    

    Good luck. I have been doing these email newsletters for a while now and they are truly pain :D