Just wondering what's the best way to go about structuring asynchronous mailers in my Rails app (using Sidekiq)? I have one ActionMailer class with multiple methods/emails...
notifier.rb:
class Notifier < ActionMailer::Base
default from: "\"Company Name\" <notify#domain.com>"
default_url_options[:host] = Rails.env.production? ? 'domain.com' : 'localhost:5000'
def welcome_email(user)
#user = user
mail to: #user.email, subject: "Thanks for signing up!"
end
...
def password_reset(user)
#user = user
#edit_password_reset_url = edit_password_reset_url(user.perishable_token)
mail to: #user.email, subject: "Password Reset"
end
end
Then for example, the password_reset mail is sent in my User model by doing...
user.rb:
def deliver_password_reset_instructions!
reset_perishable_token!
NotifierWorker.perform_async(self)
end
notifier_worker.rb:
class NotifierWorker
include Sidekiq::Worker
sidekiq_options queue: "mail"
def perform(user)
Notifier.password_reset(user).deliver
end
end
So I guess I'm wondering a couple things here...
Is it possible to define many "perform" actions in one single worker? By doing so I could keep things simple (one notifier/mail worker) as I have it and send many different emails through it. Or should I create many workers? One for each mailer (e.g. WelcomeEmailWorker, PasswordResetWorker, etc) and just assign them all to use the same "mail" queue with Sidekiq.
I know it works as it is, but should I break out each of those mail methods (welcome_email, password_reset, etc) into individually mailer classes or is it ok to have them all under one class like Notifier?
Really appreciate any advice here. Thanks!
As discussed here, Sidekiq supports delayed mailer by default, so there is no need to create separate workers:
Notifier.delay.password_reset(user.id)
I am not sure but I think its not a good idea to pass an instance in mailer action if you're using delay, so maybe its better to change the code above to :
Notifier.delay.password_reset(user.id)
Related
While upgrading my Rails 6.0 application to Rails 7.0.3, I'm facing the following problem :
Sidekiq is enqueueing all my mailer jobs through the default queue instead of the mailers queue like it used to before.
Code related to the same :
in application.rb :
config.active_job.queue_adapter = :sidekiq
My mailer :
class UserMailer < ActionMailer::Base
def staff_welcome(user, password)
#user = user
#password = password
mail(to: user.email, from: "DummyName <#{APP_CONFIG[:notifications_email]}>", reply_to: "#{APP_CONFIG[:outbound_email]}", subject: "Your staff account has been created")
end
And then, inside the controller, i'm calling the mailer as follows :
def create
#user = User.new
#user.attributes = users_params.merge(state: 'active', source: :dummy_source, activity: 1)
set_roles(users_params)
respond_to do |format|
if #user.save
if #user.staff?
UserMailer.staff_welcome(#user, params[:user][:password]).deliver_later
I'm interested in finding out why upon upgrading to Rails 7 is my queue for mailers changing? When i run the server with Rails 6, the mailers get queued via the mailers queue, but with Rails 7, the queue is default.
I understand that if i call the mailer from inside an ActiveJob, and inside the controller call the job instead of the mailer directly, I will be able to use syntax like queue_as :mailers. Is that the correct way to fix this?
I checked the changelog for sidekiq but couldn't find anything related to this? Did they change the default queue when it comes to mailers?
Also, can i set sidekiq_options with the deliver_later function?
I am using Sorcery Gem to Authenticate user in my Rails application. Everything is working fine. When user register then I am able to send user activation email. However sending email is taking long time so I was thinking to delay the email or send email in backgroud using Sidekiq. I read Sorcery documentation but couldn't find the way to delay the email using Sidekiq.
Please guide me how to delay the email send by Sorcery gem using Sidekiq.
There could be two approach to this:
Call Sidekiq worker inside Mailer
what I understand from the docs is that you can configure it to call any
Mailer. What you could do inside the method is call
MailerJob.perform_late(*arg) instead of calling mail(*args) right away
Assuming you have a code something like this (ref: https://github.com/Sorcery/sorcery/wiki/User-Activation)
# app/mailers/user_mailer.rb
def activation_needed_email(user)
#user = user
#url = activate_user_url(#user.activation_token)
mail(to: user.email, subject: 'Welcome to My Awesome Site')
end
You can change the code to this
# app/mailers/user_mailer.rb
def activation_needed_email(user)
#user = user
#url = activate_user_url(#user.activation_token)
# mail(to: user.email, subject: 'Welcome to My Awesome Site')
MyMailerWorker.perfor_later(to: user.email, subject: 'Welcome to My Awesome Site')
end
Your worker would be defined as per the sidekiq docs(ref: https://github.com/mperham/sidekiq/wiki/Getting-Started)
class MyMailerWorker
include Sidekiq::Worker
def perform(to: nil, subject: nil)
MailerIWantToCall.send_activation_email(to, subject)
end
end
P.S: Option 1 is not a clean approach but works if you are stuck with Sorcery legacy configs
Don't set mail settings for srcoery(A cleaner approach)
Do not setup mail through sorcery at all.You can revert the changes
mentioned for UserMailer on this page
(github.com/Sorcery/sorcery/wiki/User-Activation) and add a callback on User
model based on when do you want to trigger an email and that way you have
control over what to call and with sidekiq or not.
I found the solution. It's quite easy. We just need to add following line in sorcery.rb file and all sorcery mail will be handled by ActiveJob
user.email_delivery_method = :deliver_later
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'm using Rails 4.2 want to override the to field for all ActionMailer mailers for a certain environment. In this case I want to override the to field for all mailers used in Staging. My goal is for the staging environment to deliver mail exactly the same way as production, but to dump it all into a testing inbox.
I know there are services that assist with this, but my goal is to use my production API for staging delivery as a thorough test.
I'm hoping I can use a mixin or something to reset the to field before the mailer fires off.
Not sure what version of Rails you are using, but you might consider using the new mail interceptors to accomplish this.
Main advantage is that it doesn't clutter your ActionMailer classes directly.
http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
Copying their example:
class SandboxEmailInterceptor
def self.delivering_email(message)
message.to = ['sandbox#example.com']
end
end
config/initializers/sandbox_email_interceptor.rb:
ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
The simplest way would be to check which environment is running and set the to field accordingly. For example, a simple password reset mailer might look something like:
class UserMailer < ActionMailer::Base
default from: "support#example.com"
def reset_password(user_id)
#user = User.find(user_id)
#url = reset_password_users_url(token: #user.password_reset_token)
mail(to: #user.email, subject: '[Example] Please reset your password')
end
end
Now to check for the staging environment and route all of these emails to admin#example.com:
class UserMailer < ActionMailer::Base
default from: "support#example.com"
def reset_password(user_id)
#user = User.find(user_id)
#url = reset_password_users_url(token: #user.password_reset_token)
to = Rails.env.staging? ? 'admin#example.com' : #user.email
mail(to: to, subject: '[Example] Please reset your password')
end
end
I'm queuing up emails using ActionMailer and Resque. I'm not that familiar with either, but I have the basics down. Right now I'm debugging why when enqueueing emails, the email is sent but there is no body.. Mystifying.
When I execute ArtistReminderWorker.perform(8,2) the email is sent with the body. Great success!
When I execute Resque.enqueue(ArtistReminderWorker, 8, 2) the email is sent without the body. Mystery.
I thought these two statements were functionally the same, no?
At first I thought that the Resque worker wasn't able to look up the record, so I moved the database lookup from the worker into the mailer right before the mail command.
Here are my Worker and Mailer classes.
artist_reminder_worker.rb
class ArtistReminderWorker
#queue = :artist_reminders_queue
def self.perform(event_id, user_id)
ArtistReminderMailer.artist_reminder_email(event_id, user_id).deliver
end
end
artist_reminder_mailer.rb
class ArtistReminderMailer < ActionMailer::Base
default from: 'no-reply#example.com'
def artist_reminder_email(event_id, user_id)
#user = User.find(user_id)
#event = Event.find(event_id)
#url = dashboard_url
subject = "You have an upcoming gig at #{#event.venue.name}"
mail(to: #user.email, subject: subject)
end
end
My views are:
views/artist_reminder_mailer/artist_reminder_email.slim views/artist_reminder_mailer/artist_reminder_email.text.slim
Let me know if I can provide any more detail, thanks in advance!
Solved it. It wasn't actually anything I was doing wrong, but rather that I hadn't restarted the worker rake task in a while.
I guess when updating the Worker classes, you have to restart the worker.. Makes sense.