Ruby on Rails email scheduling using runner and cron

Problem
You want to send emails from a Ruby on Rails application, when there is a specific condition on a database table. If the database table gets modified by another application outside Rails you cannot use an observer model.

Solution
We already assume that:

  • You are using a database
  • You have a model named voicemail (id, number_id, audio, created_at, updated_at)
  • You have a model named number (id, voicemail_email_set, voicemail_email, ….)
  • A mail server to use (smtp in our case)
  • Another application (voice application) populates the voicemail table but with empty updated_at values

So the steps we have to follow are:

  1. Change the settings in your config/environment.rb file to use the settings for your mail server, and make sure you restart your application after the changes:
    ActionMailer::Base.smtp_settings = {
      :address        => "yourmailserver.com",
      :port           =>  25,
      :domain         => "your.domain.com",
      :authentication => :login,
      :user_name      => "your_smtp_username",
      :password       => "your_smtp_password",
      :raise_delivery_errors  => true}
  2. Create your mailer model (ie voicemail_mailer.rb), in app/models:
    class VoicemailMailer < ActionMailer::Base
       # We need the open-uri to be able to open url *** if the file to attach is in an http location ***
      require 'open-uri'
     
      def sent(email_to,email_from,email_subject,email_body,voicemail_to_send)
        # Check to see if we have a file for the email body message
        @subject    = email_subject
        @body       = email_body
        @recipients = email_to
        @from       = email_from
        @sent_on    = Time.now
        
        # Split the file in directory and filename
        file_path = File.split(voicemail_to_send)
        file_dir  = file_path[0]
        file_name = file_path[1]
        
        # Get the file
        tmp_file = open(voicemail_to_send).read
        
        part( :content_type => "application/wav", 
              :disposition => "attachment; filename=#{file_name}", 
              :transfer_encoding => "base64") do |attachment|
                attachment.body = tmp_file
        end
      end
    end
    
  3. Create your email scheduler in file lib/email_scheduler.rb:
    #!/usr/bin/env /path_to_your_app/script/runner
    
    # get all the voicemails that have not been sent yet 
    voicemails_to_email = VoiceMail.find(:all, :conditions => 'updated_at is null')
    
    # For all the voicemails we have, send them and update the field date_sent
    for vm2email in voicemails_to_email do
      # Get the number for the voicemail
      number = Number.find(vm2email.number_id)
      
      # check to see if the send to email is set for the number
      if number.voicemail_email_set
        # Get number details (email_to,email_from etc)
        email_to          = number.voicemail_email
        voicemail_to_send = vm2email.audio
        # Set other details
        email_from      = 'Service@yourdomain.com'
        email_subject   = 'Please find attached your voicemail message'
        email_body      = "Received on: #{Time.now} \n for number: #{number.phone_no}"
    
        # Now send the email
        VoicemailMailer.deliver_sent(email_to,email_from,email_subject,email_body,voicemail_to_send)
    
        # And update the record's date_sent field
        vm2email.updated_at = Time.now
        vm2email.save
      end
    end
    
  4. Create a task in your crontab that runs the scheduler (every five minutes):
    0,5,10,15,20,25,30,35,40,45,50,55 * * * * path_to_your_ror_app/lib/email_scheduler.rb