Send emails at specific times in Rails - ruby-on-rails

I want to trigger mail to be sent one hour before an appointment comes up. I am using the at field from the #appointment instance variable.
class AppointmentController < ApplicationController
def create
if DateTime.now + 1.hour > #appointment.at
AppointmentReminder.send_appointment_email(#appointment).deliver_now
end
end
end
This works if the appointment was created within an hour, but if the appointment was created in the future... then our poor customer won't be notified. Is there a mechanism where Rails can automatically deliver the email at the right time? I don't want to use a cronjob or rake task.

I'd recommend looking at background processing systems like Sidekiq or Sucker Punch which can be configured to perform jobs "later".
This way when the appointment is created you can schedule the job to execute at the correct time. You'll need to add checks to make sure when the job finally runs that it's still legitimate, etc.
http://sidekiq.org
https://github.com/brandonhilkert/sucker_punch

As you tagged your question as related to rails 4.2 then Active Job exactly what you need.

You could use whenever to run a block of code on a schedule. Say, ever 5 minutes, looks for appointments that are starting within the next hour and send an email.
To prevent multiple servers from sending an email, you could have a status on the appointment to keep track of if the email has been sent.
Then, using postgres, you can use this SQL to grab records to send and use the database to decide which server is going to send out a specific email:
Email.find_by_sql("WITH updated AS (
UPDATE emails SET status = 'processing' where lower(status) = 'new' RETURNING id
)
SELECT *
FROM emails
WHERE id IN (SELECT id FROM updated)
order by id asc
")

I will share how I have done it. It works just fine.
First, install whenever gem.
You should have your mailer. Here is mine:
class WeeklyDigestMailer < ApplicationMailer
default :from => "bla#bla.org"
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.weekly_digest_mailer.weekly_promos.subject
#
helper ApplicationHelper
def weekly_promos(suscriptor, promos)
#promos = promos
mail(:to => "<#{suscriptor.email}>", :subject => "Mercadillo digital semanal")
end
end
Of course, you need to style your view.
Then, you create a rake task (in lib/tasks). Just like this:
desc 'send digest email'
task send_weekly_email: :environment do
#promociones = Promo.where("validez > ?", Time.zone.now).order("created_at DESC")
if (#promociones.count > 0)
#suscriptors = Suscriptor.where(email_confirmation: true)
#suscriptors.each do |suscriptor|
WeeklyDigestMailer.weekly_promos(suscriptor, #promociones).deliver_now
end
end
end
Finally, you configure your schedule with whenever gem. As I want to send the mails all thrusdays at 9 am, I just put it:
every :thursday, at: '9:00 am' do # Use any day of the week or :weekend, :weekday
rake "send_weekly_email"
end
One important point: since you are using a rake task, use deliver_now instead of deliver_later because if the task finish before all emails have been sent, the rest will be undelivered.
That's all.

Related

Background thread for email sending in Ruby on Rails

I need to run a background thread in the Ruby on Rails application that should send an emails when certain date has occurred, depending on the values in DB (and email body should contain some info from this DB).
What is the best way to achieve such behavior?
I'm using Ruby on Rails 4.1.4 btw.
Thanks in advance.
You would be better off using a framework like Sidekiq or Resque than doing it yourself.
With Sidekiq, you can use the Sidekiq Pro or various third-party projects to schedule jobs. See Recurring jobs on Sidekiq's wiki for projects that provide scheduling capability.
You can use whenever gem to perform background jobs according to your requirement.
Check out the github docs here. Whenever
Ryan Bates created a great Railscast about Whenever: Cron jobs Intro in rails
In config/schedule.rb
every 2.hours do
runner User.send_email_to_users
end
In User model
def self.send_email_to_users
//write your logic here and call to action to send mails.
UserMailer.send_mail_persons(user).deliver
//pass any other data if required in arguments.
end
In app/mailers/user_mailer.rb
def send_mail_persons(user)
#user = user
mail :to => #user.email, :subject => "Amusement.", :from => MAIL_ADDRESS
end
Create a html template as per requirement, app/views/user_mailer/send_mail_persons.html.erb
lib/tasks/mail_task.rake
namespace :mail_task do
desc "Send Mail"
task :send_mail => :environment do
...
end
end
Command line
rake mail_task:send_mail

delayed_job vs. cron

I have a system where users come in to go through an application process that has multiple parts - sometimes users will save their progress and come back later.
I want to send users an e-mail if they haven't come back in 48 hours - would it be best to do this using cron, delayed_job, or whenever?
I've noticed that whenever I run operations in the console (such as bundle install or rake db:migrate) it runs cron as well, which makes me suspicious that we may have instances where users get multiple reminders in the same day.
What are your recommendations for this?
First of all, Whenever and Cron are synonymous. All Whenever does is provide a way for you to write cronjobs using Ruby (which is awesome, I love Whenever).
Delayed_job is not the answer here. You definitely want to use cronjobs. Create a method on your Application model that will get applications which have an updated_at value of < 2.days.ago and e-mail its applicant.
def notify_stale_applicants
#stale_applications = Application.where('updated_at < ?', 2.days.ago) # or 48.hours.ago
#stale_applications.each do |app|
UserMailer.notify_is_stale(app).deliver
end
end
And your UserMailer:
def notify_is_stale(application)
#application = application
mail(:to => application.user.email, :from => "Application Status <status#yourdomain.com>", :subject => "You haven't finished your Application!"
end
Using whenever to create this cron:
every :day, :at => '8am' do
runner 'Application.notify_stale_applicants'
end

Options with Rails for triggering an email to all (or some) users?

Newb question: We've got a live site with registered users. Some new functionality has been added (including support for Mailers). We would like to trigger an email to all existing users (similar but not identical to one that will now automatically go out to new users).
What options do we have for triggering the sending of that email? This message will likely only be sent once so we don't need the code (other than the message itself) in the Rails app. Nor do we really need to store who received it because it will be assumed that all users have received such a message once we can get this one out.
I'm thinking Rake task but all the examples I seem to be able to find are for build script?!? Or should we just use the Rails console in production? Perhaps get an array of all users we want to send email to and then deliver message to them?
Not sure. I haven't worked with ActionMailer much.
I'd probably do it like this:
In order to determine if the system has sent an email to a user, you should add an attribute let's say 'sent_email' which is basically just a boolean.
I'd create a cron job for a rake task that checks all users with sent_email=0. Then, I'll loop through each array and send the email and set sent_email=1. The cron job can be run daily, depending on your preference. You can use whenever gem to setup the cron job.
schedule.rb (whenever stuff)
job_type :run_script, "RAILS_ENV=:environment ruby :path/script/:task"
every 1.day do
run_script('send_email.rb')
end
script/send_email.rb
#!/usr/bin/env ruby
puts "Loading rails environment..."
require(File.dirname(__FILE__) + "/../config/environment") unless defined?(Rails)
class SendEmail
def send_email
users = User.send_email_to
users.each do |user|
OneTimeMailer.deliver_one_time_email(user)
end
end
end
mailers/one_time_mailer.rb
class OneTimeMailer < ActionMailer::Base
def one_time_email(user)
recipients user.email
from 'your system'
subject 'hello world'
body 'this is a one time email. thank you'
end
end
I hope this helps.
I suggest doing a rake task, run it once and you are done.
rails g task my_namespace my_task1 my_task2
Now you loop through your database:
namespace :sent_email_to_everyone do
desc "TODO"
task send_that_mail: :environment do
user = User.all
user.each do |user|
Yourmailer.send_email(user.email)
end
end
end
end
Now you just run it and done
rake sent_that_mail

delayed_job. Delete job from queue

class Radar
include Mongoid::Document
after_save :post_on_facebook
private
def post_on_facebook
if self.user.settings.post_facebook
Delayed::Job.enqueue(::FacebookJob.new(self.user,self.body,url,self.title),0,self.active_from)
end
end
end
class FacebookJob < Struct.new(:user,:body,:url,:title)
include SocialPluginsHelper
def perform
facebook_client(user).publish_feed('', :message => body, :link => url, :name => title)
end
end
I want execute post_on_facebook method at specific date. I store this date at "active_from" field.
Code above is working and job is executed at correct date.
But in some cases I first create Radar object and send some job to Delayed Job queue. After that I update this object and send another job to Delayed Job.
This is wrong behavior because I wan't execute job only once at correct time. In this implementation I will have 2 jobs which will be executed. How I can delete previous job so only updated one will be executed ?
Rails 3.0.7
Delayed Job => 2.1.4 https://github.com/collectiveidea/delayed_job
ps: sorry for my english I try do my best
Sounds like you want to de-queue any jobs if a radar object gets updated and re-queue.
Delayed::Job.enqueue should return a Delayed::Job record, so you can grab the ID off of that and save it back onto the Radar record (create a field for it on radar document) so you can find it again later easily.
You should change it to a before_save so you don't enter an infinite loop of saving.
before_save :post_on_facebook
def post_on_facebook
if self.user.settings.post_facebook && self.valid?
# delete existing delayed_job if present
Delayed::Job.find(self.delayed_job_id).destroy if self.delayed_job_id
# enqueue job
dj = Delayed::Job.enqueue(
::FacebookJob.new(self.user,self.body,url,self.title),0,self.active_from
)
# save id of delayed job on radar record
self.delayed_job_id = dj.id
end
end
did you try storing the id from the delayed job and then store it for possible deletion:
e.g
job_id = Delayed::Job.enqueue(::FacebookJob.new(self.user,self.body,url,self.title),0,self.active_from)
job = Delayed::Job.find(job_id)
job.delete

how do i use delayed_job on heroku to send out emails that are batched?

I read the documentation on workers and delayed_job and couldn't follow exactly, so wanted to get head-start with some strategy and sample code.
I have a controller which I use to send emails one by one. Now each day I want to check which emails need to be sent for the day, and then send them through heroku as a delayed_job.
How do I begin to approach this? thanks.
This is what I'm coming up with based on the answers:
Using the 'whenever' gem, I created the following schedule.rb
every 1.day, :at => '4:30 am' do
heroku = Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASS'])
heroku.set_workers(ENV['HEROKU_APP'], 1)
Contact.all.each do |contact|
contact_email = contact.email_today
unless contact.email_today == "none"
puts contact.first_name
puts contact_email.days
puts contact.date_entered
Delayed::Job.enqueue OutboundMailer.deliver_campaign_email(contact,contact_email)
end
end
heroku.set_workers(ENV['HEROKU_APP'], 0)
end
To determine whether I should send an email today or not, I created the method for contact.rb:
def email_today
next_event_info = self.next_event_info # invokes method for contact
next_event = next_event_info[:event]
delay = next_event_info[:delay]
if next_event.class.name == "Email" && from_today(self, next_event.days) + delay < 0 #helper from_today
return next_event
else
return "none"
end
end
Does this look right? I am developing on windows and deploying to heroku so don't know how to test it...thanks!
If you're sending emails out once a day, you probably want to start by add the cron addon to your application, which will fire a rake task once-per day.
Obviously, you'll also need to add the delayed_job plugin (http://docs.heroku.com/delayed-job). In addition, your app will need to be running at least one worker.
Then it's just a matter of doing your mail work from within your cron rake task. For example, if you had a mailer called 'UserMailer', your cron could look something like this:
#lib/cron.rb
task :cron => :environment do
User.all.each do |user|
Delayed::Job.enqueue UserMailer.deliver_notification(user)
end
end
If you're only using background tasks to send these emails, you could probably add also some logic in your cron task, and your mailer methods to add and remove workers as required, which will save you having to pay for the workers while they're not in use.

Resources