user984621
user984621

Reputation: 48453

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!

Upvotes: 2

Views: 612

Answers (1)

Oss
Oss

Reputation: 4322

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

Upvotes: 4

Related Questions