Background: I have a job that runs every morning at 6am. This job itself either sends emails or queues emails. It depends on an appointment for a user. If an appointment is scheduled for that specific day, but there is no time associated with the day, then the job runs the email right away at 6am. If on the other hand there is a specific time set for that that appointment, then the email is queued, and then formally sent 15 minutes before the appointment. So for an appointment at 2pm, the email will be queued at 6am, and formally sent at 1:45pm.
My issue is for appointments where there is a time affiliated with the appointment, i've been running into instances where an email is queued at 6am, then the appointment is cancelled at 11am, but then at 1:45pm the user still gets the appointment reminder email.
My question is, using Action Mailer and Sidekiq is it possible to delete an email that has been queued?
This is where the job is scheuduled
cron: '0 10 * * *' # 5AM/6AM Central, 12:00 Hawaii
class: 'Jobs::AppointmentsNotificationJob'
This is the notification job, and below is mailer method.
module Jobs
class AppointmentsNotificationJob
include Sidekiq::Worker
sidekiq_options unique_for: 1.day
def perform
Appointments.due_for_invite.each do |appointment|
if appointment.time_required? && appointment.scheduled_for > 16.minutes.from_now
AppointmentVisitsMailer.scheduled_immediately(appointment.id).deliver_later(wait_until: appointment.scheduled_for - 15.minutes)
else
AppointmentVisitsMailer.scheduled_immediately(appointment.id).deliver_now
end
end
end
end
end
class AppointmentVisitsMailer < ApplicationMailer
def scheduled_immediately(appointment_id)
setup_for(appointment_id)
setup_magic_link
#appointment_visit.update_column('invited_at', Time.now)
send_mail("Time for your appointment with")
end
end
When I put in breakpoints into the job, and then run the line that formally queues up the email, I get this information below. (I feel like this is where the answer is, but I don't know exactly how to formally grab these queued ones to work with)
[22] pry(#<Jobs::AppointmentsNotificationJob>)> AppointmentVisitsMailer.scheduled_immediately(appointment.id).deliver_later(wait_until: appointment.scheduled_for - 15.minutes)
Enqueued ActionMailer::MailDeliveryJob (Job ID: 0159f4de-362e-4662-8351-442a84613d86) to Sidekiq(mailers) at 2023-01-23 21:45:00 UTC with arguments: "AppointmentVisitsMailer", "scheduled_immediately", "deliver_now", {:args=>[19]}
#<ActionMailer::MailDeliveryJob:0x00007faaf948d9c8 #arguments=["AppointmentVisitsMailer", "scheduled_immediately", "deliver_now", {:args=>[19]}], #job_id="0159f4de-362e-4662-8351-442a84613d86", #queue_name="mailers", #priority=nil, #executions=0, #exception_executions={}, #timezone="Central Time (US & Canada)", #scheduled_at=1674510300.0, #provider_job_id="48a5d4fa311cb65a3c97de0b">
Is it possible to grab ActionMailer::MailDeliveryJob:0x00007faaf948d9c8? (I've tried here, with little luck, but I essentially need to keep that guy from formally sending if the appointment has been cancelled.
Related
Something simple, I'm sure but I've been searching and can't find an answer.
in brief: I want to set up a daily mailer to email a lists of tasks daily.
I have a worker (scheduled every minute, and only puts'ing for dev):
class DailyReminderWorker
include Sidekiq::Worker
include Sidetiq::Schedulable
recurrence do
hourly.minute_of_hour(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59)
end
def perform
User.find_each do |user|
#user = user.name
puts "user name is #{#user}"
#reminder = Remindarrr.where(user_id: user.id)
#reminder.each do |r|
puts r.title
end
end
end
end
I know I can call this with the following in a controller:
DailyReminderWorker.perform_async
This works and outputs every minute but every time the page refreshes it also fires. (less useful for a daily mailer.)
How do you call the worker to queue the job without it firing immediately?
Where would you put the perform.async?
Thanks a lot!
If you are on a Linux platform, just use cron. very easy to setup.
When using Rails Action Mailer, you have the option to use deliver_now to send the email immediately, or deliver_later to send through asynchronously using Active Job. If Active Job is not specified an adapter, it will use an in-process thread pool which would not persist if the server were to stop. Alternatively, I can create a Job to manage my e-mails, which I can then call with perform_now or perform_later, which as I understand is more or less the exact same thing as deliver_now and deliver_later.
My question is, if I specify an adapter, let's say Sidekiq, and then have a database to store my jobs, why would I create a job to handle my e-mails? Is there any additional benefit, or is it an unnecessary step? On a slightly different note, if I did want to create a job for the process, would my email method need to have deliver_now or simply nothing at all? I presume if the e-mail were to say deliver_later, would it knock back the email to the end of the queue and force it wait again until it is sent?
To illustrate, if I have no Job set up to handle my emails, I could simply have:
class UserMailer < ActionMailer::Base
def send_email(user)
mail(to: user.email, subject: "My Subject")
end
end
To call, I would use:
UserMailer.send_email(my_user).deliver_later.
However, if I had wanted to add a job, and both options were called with UserJob.perform_later(user), would my setup be:
class UserJob < ActiveJob::Base
def perform(user)
UserMailer.send_email(user).deliver_now
end
end
Or
def perform(user)
UserMailer.send_email(user)
end
Lastly, I don't think this makes sense, but what would happen if I used:
def perform(user)
UserMailer.send_email(user).deliver_later
end
I am using Rails with Devise confirmable. When users sign up Devise automatically sends them a confirmation email. If they haven't confirmed after three days, I would like to send them another confirmation email with a new token. If they still do not confirm after this second email, we should send them no more. Mapped out on a timeline:
Day 1: User signs up & receives confirmation email; does not confirm
Day 2: User does not confirm
Day 3: Send another email prompting them to confirm; does not confirm
Day 4: User does not confirm; no more emails sent
Day 5: User does not confirm; no more emails sent
Day 6: etc.
I'd rather not add a new column to my Users model that keeps track of how many times they've been sent a confirmation email since I think I can do it in a Worker that runs every day using the information we already have. However I'm struggling a bit with getting the logic right.
Here's what I have so far:
# workers/resend_confirmation_worker.rb
class ResendConfirmationWorker
include Sidekiq::Worker
sidekiq_options queue: :resend_confirmation, retry: false
def perform
users = User.where('confirmation_sent_at IS NOT NULL and
confirmed_at IS NULL')
users.each do |user|
return if user.created_at < Time.zone.now - 4.days || user.created_at > Time.zone.now - 3.days
user.resend_confirmation!
end
end
# config/clock.rb
module Clockwork
every(1.day, 'Resend confirmation') { ResendConfirmationWorker.perform_async }
end
Appreciate any help or suggestions for improvement.
If you are going to run the worker once a day, you could probably use something like:
User.where('
confirmed_at IS NULL
AND created_at >= ?
AND created_at <= ?
', 2.days.ago.beginning_of_day, 2.days.ago.end_of_day)
If you run this on Wednesday, it fetches all unconfirmed users that are registered on Monday.
d = 2.days.ago
users = User.where.not(confirmation_sent_at: nil)
.where(confirmed_at: nil)
.where(created_at: d.beginning_of_day..d.end_of_day)
users.find_each do |user|
user.resend_confirmation!
end
The answer posted by max is almost perfect however you need to use user.send_confirmation_instructions instead of resend_confirmation!, and if the worker ran more than once in a day by accident (e.g. due to a dyno restart), people could potentially get too many confirmation emails.
Now all I'm trying to figure out is why my tests aren't working.
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.
I have problem with sending recurring mails with Sidekiq and Sidetiq. I'v tried almost everything and I didn't find the solution.
I have Sidekiq worker which looks like this:
class InvoiceEmailSender
include Sidekiq::Worker
include Sidetiq::Schedulable
recurrence {minutely(2)}
def perform(invoice_id, action)
#invoice = Invoice.find(invoice_id.to_i)
if action == "invoice"
send_invoice
else
send_reminder
end
end
private
def send_invoice
if #invoice.delivery_date == Date.today
InvoiceMailer.delay.send_invoice(#invoice)
else
InvoiceMailer.delay_for(#invoice.delivery_date.to_time).send_invoice(#invoice)
end
end
def send_reminder
InvoiceMailer.delay.send_invoice_reminder(#invoice) unless #invoice.paid?
end
end
End in controller I use it in this way:
InvoiceEmailSender.perform_async(#invoice.id, "invoice")
And when I try to send this emails I have the following error in sidekiq console:
2014-08-26T05:36:01.107Z 4664 TID-otcc5idts WARN: {"retry"=>true, "queue"=>"default", "class"=>"InvoiceEmailSender", "args"=>[1409031120.0, 1409031240.0], "jid"=>"06dc732831c24e1a6f78d929", "enqueued_at"=>1409031120.7438812, "error_message"=>"Couldn't find Invoice with 'id'=1409031120", "error_class"=>"ActiveRecord::RecordNotFound", "failed_at"=>1409031249.1003482, "retry_count"=>2, "retried_at"=>1409031361.1066737}
2014-08-26T05:36:01.107Z 4664 TID-otcc5idts WARN: Couldn't find Invoice with 'id'=1409031120
2014-08-26T05:36:01.107Z 4664 TID-otcc5idts WARN: /home/mateusz/.rvm/gems/ruby-2.0.0-p0#rails4/gems/activerecord-4.1.2/lib/active_record/relation/finder_methods.rb:320:in `raise_record_not_found_exception!'
In sideiq web monitor in scheduled tab it looks like this:
Please help because I have not idea what is going on...
The data passed in looks like epoch timestamps, turns out Sidetiq passes the last and current times as the 2 parameters to your worker, according to the documentation.
I'm not sure how you go about having custom parameters with a scheduled worker, you'll probably need a different strategy like instead of trying to create more scheduled workers, just have 1 (or two, since it looks like you made this class do 2 jobs) scheduled worker(s) that processes a list of work to do every so often.