Kevin Behan
Kevin Behan

Reputation: 496

Send emails at specific times in 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.

Upvotes: 1

Views: 2905

Answers (4)

Juanse Cora
Juanse Cora

Reputation: 935

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 => "[email protected]"
  # 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.

Upvotes: 0

John Naegle
John Naegle

Reputation: 8247

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

")

Upvotes: 0

atomdev
atomdev

Reputation: 321

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

Upvotes: 0

Philip Hallstrom
Philip Hallstrom

Reputation: 19879

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.

Upvotes: 1

Related Questions