views:

459

answers:

4

Hi, I would like to be able to send a string of emails at a determined interval to different recipients.

I assign to each Contact this series of Emails called a Campaign, where Campaign has Email1, Email2, etc. Each Contact has a Contact.start_date. Each Email has email.days which stores the number of days since a Contact's start-date to send the email.

For example: Email1.days=5, Email2.days=7, Email3.days=11.

Contact1.start_date = 4/10/2010; contact2.start_date = 4/08/2010

IF today is 4/15, then Contact1 receives Email 1 (4/15-4/10 = 5 days) IF today is 4/15, then Contact2 received Email 2 (4/15 - 4/8 = 7 days).

What's a good action to run every day using a cron job that would then follow these rules to send out emails using ActionMailer?

NOTE: The question isn't about using ActionMailer. It is about doing the "math" as well as the execution. Which email to send to whom? I am guessing it has to do with some version of Date - Contact[x].start_date and then compare against email[x].days but I'm not exactly clear how. Thanks.

I'd like guidance on whether to use date.today versus time.now as well.

Note: the intent is that an individual person may need to schedule individual follow-up on a consistent basis. Rather than having to remember when to follow up which email with whom, it would just follow a pre-determined campaign and send for that person.

So it's not a "bulk mail" -- it's really automating the follow-up for individual correspondence.

+1  A: 

I think it would be much easier and more secure (you don't have to worry on authentication and so on) to create a rake task to send the emails. Also you don't have to worry about a possibly very long running request. Just create a file RAILS_ROOT/lib/tasks/email.rake

namespace :email do 
  desc "Sends scheduled emails"
  task :send_scheduled => :enviroment do 
    Email.send_scheduled_emails
  end
end

and in RAILS_ROOT/app/email.rb

class Email < ActiveRecord::Base      
   # ...

   def self.send_scheduled_emails 
     #send your emails ...
   end
end

Then create a cron job

0 0 * * * user cd /your/rails/app/ && RAILS_ENV=production rake emais:send_scheduled

to send the emails every night at 12:00.

gregor
Voted down because it doesn't show how to determine when to send each type of Email.
Sixty4Bit
Right: I need to know which email and when to send it...different contacts have different start dates. I can calculate an interval and come up with situation where the interval = 0...but I'd have to loop through each contact and through all events in the campaign.
Angela
+5  A: 

I would use DelayedJob for this ( assuming you are not sending large number of emails emails a day, i.e. 100's of thousands per day etc.)

class Email < ActiveRecord::Base
  belongs_to :campaign
  after_create :schedule_email_dispatch

  def schedule_email_dispatch
    send_at(campaign.created_at + self.days.days, :send_email)
  end

  def send_email
  end
end

Run the workers using the rake task:

rake jobs:work

Every time a new Email object is created a delayed job item is added to the queue. At the correct interval the email will be sent by the worker.

@campaign = Compaign.new(...)
@campaign.emails.build(:days => 1)
@campaign.emails.build(:days => 2)
@campaign.save # now the delay

In the example above, two delayed job entries will be created after saving the campaign. The jobs will be executed 1 and 2 days after the creation date of the campaign.

This solution ensures emails are sent approximately around the expected schedule times. In a cron job based solution, disptaching happens at the cron intervals. There can be several hours delay between the intended dispatch time and the actual dispatch time.

If you want to use the cron approach do the following:

class Email < ActiveRecord::Base
  def self.dispatch_emails
    # find the emails due for dispatch
    Email.all(:conditions => ["created_at <= DATE_SUB(?, INTERVAL days DAY)", 
             Time.now]).each do |email|
      email.send_email
    end
  end
end

In this solution, most of the processing is done by the DB.

Add email.rake file in lib/tasks directory:

task :dispatch_emails => :environment do
  Email.dispatch_emails
end

Configure the cron to execute rake dispatch_emails at regular intervals( in your case < 24 hours)

KandadaBoggu
This is a cool way to handle the problem. Scheduling at time of creation means you don't have to worry about doing the math when trying to determine what needs to be run. If the questioner doesn't want to use DJ (to avoid complexity), they could still do the same trick by storing the dates when the emails should go out. However, since you need the capability already, I bet the questioner will need it again in the future, so may as well pay the price now.Don't forget to pass in a "type designator" or something to Email#build so you know which email to send. Wish I could do multiple up votes.
Sixty4Bit
Interesting....so when a new contact is created (each Contact will be added into the future with a different date_entered date)...how do I make sure every Contact gets the email at the appropriate Interval. It is not only emails that have varying intervals, but the Contacts (recipients), as well.
Angela
Yes, skimming this, it looks like it is referencing the campaign dates, but not the indnividual contact dates...the contacts would be in the thousands....each with their own date (many of them coinciding), but many not.
Angela
Do you need to send a bulk email to all the contacts? Or individual emails? The solution above can be tailored for both scenarios(you do need to make some changes to the code). Your data model requirements are not very clear. Update your question with additional details about your requirement.
KandadaBoggu
I *think* this gives me the specifics on how to do this....it's a smart idea, just need to think through it....using the created_at method which comes with rails and then taking on the individual information seems right...
Angela
For this to work, I need to used Delayed_Job, is that right? That comes with the :methods I need?
Angela
Yes, use this version: http://github.com/collectiveidea/delayed_job.It has the method called `send_at` as indicated in my code.
KandadaBoggu
+3  A: 

I would create a rake task in RAILS_ROOT/lib/tasks/email.rake

namespace :email do
  desc "send emails to contacts"
  task :send do
    Email.all.each do |email|
      # if start_date is a datetime or timestamp column
      contacts = Contact.all(:conditions => ["DATE(start_date) = ?", email.days.days.ago.to_date])
      # if start_date is a date column
      contacts = Contact.all(:conditions => { :start_date => email.days.days.ago.to_date })
      contacts.each do |contact|
        #code to send the email
      end
    end
  end
end

Then I would use a cronjob to call this rake task every day at 3 a.m.:

0 3 * * * app_user cd RAILS_APP_FOLDER && RAILS_ENV=production rake email:send
jigfox
@Angela, This has the date comparision calculation that you wanted. `email.days` => say, 5, `5.days.ago.to_date` => whatever date is five days ago.
Tim Snowhite
@Jens Fahnenbruck: `:start_date, email.days.days.ago.to_date` should be `:start_date => email.days.days.ago.to_date`
Tim Snowhite
@Tim Snowhite....ah, okay, I need to digest email.days.days.ago.to_date ... where can I go to parse that out? I got lost after email.days....
Angela
@Tim Snowhite: You're right, I have corrected it now
jigfox
@Angela, You're setting the email.days part, so that will return a number; if the database field was named `emails.number_of_days_to_send_from_now` the line would read `email.number_of_days_to_send_from_now.days.ago.to_date`; calling `number.days.ago` gives a Time object representing `number * 24 hours` before the current time (`Time.now`). Time#to_date just lops off the Hh:Mm:Ss part of the time object and converts it to a date object. So the overall flow is `email.days` -> `5` or whatever you've stored in the db for that number, then, `5.days.ago.to_date` -> five days ago's date.
Tim Snowhite
@Angela, It might make sense to make a method on Email that encapsulates this days.ago.to_date chain, if it seems complicated. You could call the method `for_contacts_made_on`, like `def for_contacts_made_on; self.days.days.ago.to_date; end` (replacing the ;'s with newlines.) Then the above solution code's queries would look like `:conditions => ["DATE(start_date) = ?", email.for_contacts_made_on]`.
Tim Snowhite