Send email asynchroniously using Sidekiq.
Create your mailer us usual:
# app/mailers/users_mailer.rb
class UsersMailer < ActionMailer::Base
def welcome_email(user_id)
@user = User.find(user_id)
mail( :to => @user.email,
:subject => "Welcome"
) do |format|
format.text
format.html
end
end
end
Views for email:
app/views/users_mailer/welcome_email.html.erb - HTML version
app/views/users_mailer/welcome_email.text.erb - TEXT version
Send email:
user = User.find(1)
mail = UsersMailer.welcome_email(user.id)
#mail.deliver_now
mail.deliver_later
Gemfile:
gem 'sidekiq'
Redis provides data storage for Sidekiq. It holds all the job data along with runtime and historical data
To make #deliver_later
work we need to tell ActiveJob to use Sidekiq.
As long as Active Job is setup to use Sidekiq we can use #deliver_later.
# config/initializers/active_job.rb
config.active_job.queue_adapter = :sidekiq
Environment file:
# config/environments/development.rb
Rails.application.configure do
...
config.active_job.queue_adapter = :sidekiq
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { ... }
end
Read more about ActionJob and Sidekiq: https://github.com/mperham/sidekiq/wiki/Active-Job
config/sidekiq.yml:
---
:concurrency: 1
:queues:
- default
- mailers
Specify Redis namespace for different environments:
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: 'redis://localhost:6379/0', namespace: "app3_sidekiq_#{Rails.env}" }
end
Sidekiq.configure_client do |config|
config.redis = { url: 'redis://localhost:6379/0', namespace: "app3_sidekiq_#{Rails.env}" }
end
By default, jobs to deliver emails will be placed in queue named :mailers. To change queue name for ActionMailer use this config:
# config/environments/development.rb
config.active_job.queue_adapter = :sidekiq
config.active_job.queue_name_prefix = "mysite"
config.active_job.queue_name_delimiter = "_"
This will use queues named :mysite_mailers
, etc.
!!! important. You may need to include new queue names in sidekiq.yml file:
# config/sidekiq.yml
---
:concurrency: 1
:queues:
- default
- mailers
- mysite_default
- mysite_mailers
Read this: https://github.com/mperham/sidekiq/wiki/Devise
bundle exec sidekiq --environment development -C config/sidekiq.yml
Use god for monitoring and running sidekiq automatically: https://gist.github.com/maxivak/05847dc7f558d5ef282e
In these tests we do not use Sidekiq.
test environment:
# config/environments/test.rb
Rails.application.configure do
...
config.active_job.queue_adapter = :test
config.action_mailer.perform_deliveries = true
config.action_mailer.delivery_method = :test
config.action_mailer.raise_delivery_errors = true
Test that deliver_later method was called:
user = User.first
#
message_delivery = instance_double(ActionMailer::MessageDelivery)
expect(UsersMailer).to receive(:welcome_email).with(user.id).and_return(message_delivery)
expect(message_delivery).to receive(:deliver_later)
#
mail = UsersMailer.welcome_email(user.email)
mail.deliver_later
test environment:
# config/environments/test.rb
Rails.application.configure do
...
config.active_job.queue_adapter = :sidekiq
config.action_mailer.perform_deliveries = true
config.action_mailer.delivery_method = :test
config.action_mailer.raise_delivery_errors = true
spec/rails_helper.rb:
# sidekiq
require 'sidekiq/testing'
Sidekiq::Testing.fake! # by default it is fake
User Sidekiq::Worker.jobs.size to see the number of jobs in the queue.
Test that email was enqueued:
RSpec.describe "Test sending email with sidekiq", :type => :request do
it 'send email to sidekiq' do
user = User.first
expect{
UsersMailer.welcome_email(user.id).deliver_later
}.to change( Sidekiq::Worker.jobs, :size ).by(1)
end
end
This guide is extremely helpful, thank you!
I wanted to mention an issue I had when testing if the email was enqueued with Sidekick - in case it helps anyone else. Using argument syntax with
change
rather than block syntax wasn't working for me. The value of.size
didn't change.I believe the reason is that with argument syntax,
Sidekiq::Worker.jobs
is evaluated before theexpect
runs, and it returns a different array instance each time it's executed.So checking
.size
in this case was returning the size of thejobs
instance before theexpect
block executes.Changing to a block syntax so that
.jobs
is evaluated both before and after theexpect
block solved my issue: