views:

570

answers:

6

How would I go about sending an email to a user, say, 48 hours after they sign up, in Ruby on Rails? Thanks!

+1  A: 

First you're going to need a running daemon or background service which can poll your queue (probably from in a database) every few minutes.

The algorithm is pretty simple. Record the time of the user event in the queue. When the daemon checks that item in the queue, and the time difference is greater than 48 hours, prepare the e-mail to send.

Joseph Daigle
+1  A: 

You can queue jobs with a delay using async observer. Ideally, anything you have that isn't known to be instant (or very close to it) all the time should pass through something like that.

Dustin
+6  A: 

As Joseph Daigle mentioned, you need to obviously record the exact date and time the user registered. After that, you need a cron running every certain number of minutes (every hour, for example) checking to see if there's any new users whose registration time is greater than 48 hours, send a mail to said user and mark that user as already emailed, so you don't email them again.

As per the actual mail sending, check out the following documentation page: http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer

It has all you need to know to send mails with RoR.

Dave
Just a note on setting a Cron Job - easiest way is to write your code as a Rails Controller and have Cron use WGET or CURL to call the url.
Toby Hede
Excelent point. I forgot to mention that part in the answer, but yeah, definitely handle the actual cron functionality as a controller and maybe a bit of a view if you want reports of which users have been mailed.
Dave
+3  A: 

I recommend that you use the latest version of BackgrounDRb to handle this. You can read about BackgrounDRb here: http://backgroundrb.rubyforge.org/

In order to queue a message for later delivery, the BackgrounDRb client code (in your application model's after_create callback, maybe) could look something like this:

MiddleMan(:email_worker).enq_send_email_task(:message => @message, 
  :job_key => "notify1",
  :scheduled_at => Time.now + 48.hours)

You'd have to build a BackgrounDRb worker to handle sending the email:

# RAILS_ROOT/lib/workers/email_worker.rb
class EmailWorker < BackgrounDRb::MetaWorker
  set_worker_name :email_worker
  def send_email_task(message)
    # ... Code to send the email message 
  end
end

Note that in order to use BackgrounDRb in this way, you have to use persistent job queues, so make sure you run the migration included with BackgrounDRb to set up the persistence table in your application.

BackgrounDRb is started separately from Rails (mongrel, apache, etc) using 'script/backgroundrb start', so make sure that you add the daemon to whatever process monitoring you're using (god, monit, etc) or that you create an /etc/init.d script for it.

Pete
A: 

I think I'd be inclined to store the need for the email and the earliest time after which it should be sent, somewhere separate, then have my things-to-do task look at that. That way I only have to process as many records as there are emails to be sent, rather than examine every user every time, which would either get tedious or require an otherwise probably unnecessary index. As a bonus, if I had other tasks to be performed on some sort of a diarised basis, the same construct would be useful with little modification.

Mike Woodhouse
+1  A: 

I wrote a plugin called acts_as_scheduled that may help you out.

acts_as_scheduled allows you to manage scheduled events for your models.

A good example of this is scheduling the update of RSS Feeds in a background process using Cron or BackgroundRB.

With acts_as_scheduled your schedule manager can simply call "Model.find_next_scheduled()" to grab the next item from the database.

How I would approach this is by creating a scheduling controller, that will query the database for the next_scheduled and then use a mailer to send the message. The you set up a Cron Job to call the controller periodically using WGET or CURL. The advantage of the Cron/Controller approach is that no further infrastructure or configuration is required on the server and you avoid complicated threading code.

Toby Hede