Sending emails based on intervals using Ruby on Rails - ruby-on-rails

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.

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. They are 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)

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

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.

I am using rufus-scheduler for scheduled email and twitter updates. You should check it.

I use ar_mailer gem
http://seattlerb.rubyforge.org/ar_mailer/
http://github.com/adzap/ar_mailer
http://blog.segment7.net/articles/2006/08/15/ar_mailer

Related

sending mail at user assigned time and updating status in database in rails error

I want to send mail at remind_at time of each stage. i am using whenever gem to schedule task. There are user model that contains email of multiple user with role manager, and each manager has one_to_many association with project and project has one_to_many association with stage.
while sending email to each user i want to update attribute mail_status of stage and mail subject change to stage.name for each user. how can i achieve such goal??
stage.rb
def check_project_activity
current_date = Time.now.strftime("%Y-%m-%d").to_s
#stages = Stage.all
#stages.each do |stage|
ProjectMailer.activity_reminder(stage).deliver and stage.mail_status = true and stage.save! if stage.remind_at.strftime("%Y-%m-%d").to_s == current_date
end
end
schedule.rb
every 1.day, at: '4:30 am' do
runner 'stage.project_activity_check'
end
activity_mailer.rb
def activity_reminder(stage)
#stage = stage
mail(:to => User.joins(projects: :stages).where(role: 'manager', stages: { remind_at: Time.now.strftime("%Y-%m-%d") }).pluck(:email), :subject => "activity reminder " + stage.name)
end
i took reference from this post - Sending emails on user-selected dates
but my implementation is not working.
Here is code that i used but problem is it sends email once to all user but i want dynamic change is email subjet according to user and also template body changes-
activity_reminder.rb
desc 'activity reminder email'
task activity_reminder_email: :environment do
ProjectMailer.activity_reminder(self).deliver!
end
project_mailer.rb
def activity_reminder(stage)
#stage = stage
mail(:to => User.joins(projects: :stages).where(role: 'manager', stages: { remind_at: Time.now.strftime("%Y-%m-%d") }).pluck(:email), :subject => "Project Activity Remainder")
end
schedule.rb
every 1.day, at: '4:30 am' do
rake 'activity_reminder_email'
end
it works fine but i want email subject as stage.name to be according to each user and also it should loop though each task and update stage.email_status for executed task.
I think you can do some changes:
You're formatting the remind_at values as a stringified version of a Y-m-d date, in that case, you could just compare them as dates. to_date is enough.
Instead of selecting everything and filtering then, you can filter the rows within your SQL query; cast the remind_at to date and use your RDBMS function to get the current date, after that the comparison remains the same.
Left it untouched, but you could probably select the exact columns you're using in your query. This is often preferred so you get what you just need, nothing more, nothing less.
Prefer && over and, since they don't have the same precedence (&& being higher), which may led to inconsistences in your code (this isn't Python).
You can use update instead of a two-lines assignment.
def check_project_activity
Stage.where('remind_at::date = CURRENT_DATE').each do |stage|
ProjectMailer.activity_reminder(stage).deliver
stage.update(mail_status: true)
end
end
remind_at::date = CURRENT_DATE is PostgreSQL specific, but it can be easily adapted.

Delayed Job not saving record sometimes

I have a recurring delayed job that sends out a confirmation email and marks the order as completed so that the next time the delayed job runs the order will not be re-processed.
Sometimes (seems to be when a certain string is tied to a promo code field but that might just be a coincidence) the job processes and sends the email but does not save the record and mark it as completed. I have used IRB to set the record to what the code would and verified that the record is valid.
Any ideas why this might be happening or has anyone seen this happen?
class PaymentEmailAndLock < Struct.new(:blank)
include Delayed::RecurringJob
run_at '8:00pm'
run_every 1.day
timezone 'US/Eastern'
queue 'dailyjobs'
def perform
time_set = 30.hours.ago..2.hours.ago
#mail_and_lock = Cart.where(updated_at:time_set,payment_sent:true,complete_order_lock:false)
#mail_and_lock.each do |obj|
obj.complete_order_lock = true
obj.survey_available = true
obj.save
if obj.payment == 1
MessageMailer.delay(queue: 'mailers').message_payment_paper(obj.cust_email,obj)
else
MessageMailer.delay(queue: 'mailers').message_payment_digital(obj.cust_email,obj)
end
end
end
end

Automatically change status of post Rails

In my rails application I have a job model which I want the status to be changed automatically to "archived" after 30 days from approval by the admin is this possible? if so what is the way to do it?
I would add an attribute named "archive_time" as a datetime when it enters the approved state.
Then you can set up a rake task to set the archived state and where the archive_time is in the past. This might look like this:
jobs = Job.where("state = ? and archive_time >= ?", 'approved', Time.now)
jobs.each {|job| job.archive }
Then schedule the rake task to be run once a day. I would use cron to achieve this.
A rake task or background job (such as Active Job or Delayed Job ) can do the trick however you might not need them in this case. If you are dealing with timestamps in your database, you can create a scope or a method to mark jobs as archived.
For instance, if you have a column named approved_at that is a datetime. When you approve a job, you set the approved_at = Time.now.
Now you can create a method that indicates if the job is archived:
def is_archived?
approved_at && approved_at < Time.now - 30.days
end
And to get all the archived jobs, you can create a scope:
scope :archived, -> { where('approved_at IS NOT NULL AND approved_at <?', Time.now - 30.days)}

Rails Delayed Job Continuously Running

I created a batch email system for my website. The problem I have, which is terrible, is it continuously sends out emails. It seems the job is stuck in an infinite loop. Please advise. It is crazy because on my development server only one email is sent per account, but on my production server I received 5 emails. Thus, meaning all users of my site received multiple emails.
Controller:
class BatchEmailsController < ApplicationController
before_filter :authenticate_admin_user!
def deliver
flash[:notice] = "Email Being Delivered"
Delayed::Job.enqueue(BatchEmailJob.new(params[:batch_email_id]), 3, 10.seconds.from_now, :queue => 'batch-email', :attempts => 0)
redirect_to admin_batch_emails_path
end
end
Job in the lib folder:
class BatchEmailJob < Struct.new(:batch_email_id)
def perform
be = BatchEmail.find(batch_email_id)
if be.to.eql?("Contractors")
cs = Contractor.all
cs.each do|c|
begin
BatchEmailMailer.batch_email(be.subject, be.message, be.link_name, be.link_path, be.to, c.id).deliver
rescue Exception => e
Rails.logger.warn "Batch Email Error: #{e.message}"
end
else
ps = Painter.all
ps.each do |p|
begin
BatchEmailMailer.batch_email(be.subject, be.message, be.link_name, be.link_path, be.to, p.id).deliver
rescue Exception => e
Rails.logger.warn "Batch Email Error: #{e.message}"
end
end
end
end
end
Delayed Job Initializer:
Delayed::Worker.max_attempts = 0
Please provide feedback on this approach. I want to send out the batch email to all users, but avoid retrying multiple times if something goes wrong. I added rescue block to catch email exceptions in hope that the batch will skip errors and continue processing. As a last resort do not run again if something else goes wrong.
What one of my apps does which seems to work flawlessly after millions of emails:
1) in an initializer, do NOT let DelayedJob re-attempt a failed job AND ALSO not let DJ delete failed jobs:
Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.max_attempts = 1
2) Scheduling a mass email is 1 job, aka the "master job"
3) When THAT jobs runs, it spawns N jobs where N is the number of emails being sent. So each email gets its own job. (Note: if you use a production email service with 'batch' capability, one "email" might actually be a batch of 100 or 1000 emails.)
4) We have an admin panel that shows us if we have any failed jobs, and if they are, because we don't delete them, we can inspect the failed job and see what happened (malformed email address etc)
If one email fails, the others are un-affected. And no email can ever be sent twice.

Where does an if/than statement that needs to run constantly go in rails?

Right now I'm building a call tracking app to learn rails and twilio. The app has 2 relevant models ; The Plans model has_many users. The plans table also has the value max_minutes.
I want it to make it so that when a particular user goes over their max_minutes, their sub account is disabled, and I can also warn them to upgrade in the view.
To do this, here's a parameter I created in the User class
def at_max_minutes?
time_to_bill=0
start_time = Time.now - ( 30 * 24 * 60 * 60) #30 days
#subaccount = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
#subaccount.calls.list({:page => 0, :page_size => 1000, :start_time => ">#{start_time.strftime("%Y-%m-%d")}"}).each do |call|
time_to_bill += (call.duration.to_f/60).ceil
end
time_to_bill >= self.plan.max_minutes
end
This allows me to run if/else statements in the view to urge them to upgrade. However, I'd also like to make an if/else statement where, if at_max_minutes? than the user's twilio subaccount is disabled, else, it's enabled.
I'm not sure where I would put that though in rails.
It would look something like this
#client = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
#account = #client.account
if at_max_minutes?
#account = #account.create({:status => 'suspended'})
else
#account = #account.create({:status => 'active'})
end
BUT, I'm not sure where I would put this code, so that it's active all the time.
How would you implement this code, for the functionality to work?
Instead of constantly computing the total minutes used in at_max_minutes?, why not keep track of a user's used minutes, and set the status to "suspended" on the transition (when used minutes goes over max_minutes). Then your view and call code would only have to check status (you may also want to store status directly on user, to save API calls over to Twilio).
Add to User model:
used_minutes
When every call ends, update minutes:
def on_call_end( call )
self.used_minutes += call.duration_in_minutes # this assumes Twilio gives you a callback and has the length of the call)
save!
end
Add an after_save to User:
after_save :check_minutes_usage
def check_minutes_usage
if used_minutes >= plan.max_minutes
#account = #account.create({:status => 'suspended'})
else
#account = #account.create({:status => 'active'})
end
end
You're going to have to do some sort of scheduled background job for this check if you want it to be "active all the time". I'd recommend resque with resque-scheduler, which is a pretty good scheduling solution for Rails. Basically what you to do is to make a job, which executes that second block of code you specified, and have it run on a regular interval (maybe every 2 hours).

Resources