Rails rake task that loops through multiple email recipients without using bcc - ruby-on-rails

I have a Rails rake task that I'm using to send emails and the only way I can get my Mailer to send to multiple recipients is by collecting them in the Mailer and then listing them as bcc recipients like this:
class SubscribersMailer < ApplicationMailer
def send_daily_updates
#subscribers = Subscriber.all
recipients = #subscribers.collect(&:email).join(",")
#subscribers.each do |subscriber|
mail(to: "***#example.com.com", subject: "Email subject", :bcc => recipients)
end
end
end
And in my task, I'm just calling this:
SubscribersMailer.send_daily_updates.deliver_now
When I run the rake task it successfully sends the email to each address, but this doesn't seem like the ideal way to do this. Sending out mass emails with a bunch of addresses in the bcc field is a good way to get it marked as spam.
What is a better way to loop through the recipients? Should I do this in the rake task or in the Mailer?

I advice you to use only one subscriber in mailer and call it for each subscriber at the rake task. Like
class SubscribersMailer < ApplicationMailer
def send_daily_updates(subscriber)
mail(to: subscriber.email, subject: "Email subject")
end
end
Subscriber.find_each do |subscriber|
SubscribersMailer.send_daily_updates(subscriber).deliver_now
end
This will generate a bunch of emails, but subscribers will see only their email address in received letter. That is more secure (you not expose all of you subscribers email addresses to each subscriber) and get lower risk that it will be marked as spam.
Also I advice you to use some background workers instead of rake tasks (Sidekiq, Rescue, and etc.)

Related

Best practice on structuring asynchronous mailers (using Sidekiq)

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)

Email body not sending with Action Mailer and Resque

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.

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

How does one use delayed_job to make an Rails 3.0 ActionMailer run asynchronously? Encountering ArgumentErrors

I'm trying to delay a notification email to be sent to users upon signing up to my app. The emails are sent using an ActionMailer which I call InitMailer. The way I am trying to delay the jobs is using collectiveidea's delayed_job https://github.com/collectiveidea/delayed_job. To do this you can see that i specify handle_asynchronously after defining the method initial_email:
class InitMailer < ActionMailer::Base
default :from => "info#blahblahblah.com"
def initial_email(user)
#user = user
#url = "http://www.blahblahblah.com"
mail(:to => user.email,
:subject => "Welcome to my website!"
)
end
handle_asynchronously :initial_email
end
However, I encounter an argument error in my log file "delayed_job.log":
Class#initial_email failed with ArgumentError: wrong number of arguments (1 for 0) - 5
failed attempts
For your information, the email is sent in a controller using the line:
#user = InitUser.new(params[:init_user])
InitMailer.delay.initial_email(#user)
Additionally, when I set up my code without the delay, the emails were sent out without problem (except for the fact that it slowed down my app waiting for gmail servers)
Where is causing the errors here? How can I get the delayed mail to send properly?
Due to the way that Rails3 implements mailers, there are some unusual workarounds for delayed_jobs. For instance, you have seen that to delay the mailing, you write
ExampleMailer.delay.example(user)
While typically you would have to write handle_asynchronously after the method definition, in the case of mailers this declaration (for some reason) prevents that delayed job from working.
So in this code, drop the declaration entirely:
class InitMailer < ActionMailer::Base
default :from => "info#blahblahblah.com"
def initial_email(user)
#user = user
#url = "http://www.blahblahblah.com"
mail(:to => user.email,
:subject => "Welcome to my website!"
)
end
#No handle_asynchronously needed here
end

Rails - How do you test ActionMailer sent a specific email in tests

Currently in my tests I do something like this to test if an email is queued to be sent
assert_difference('ActionMailer::Base.deliveries.size', 1) do
get :create_from_spreedly, {:user_id => #logged_in_user.id}
end
but if i a controller action can send two different emails i.e. one to the user if sign up goes fine or a notification to admin if something went wrong - how can i test which one actually got sent. The code above would pass regardless.
As of rails 3 ActionMailer::Base.deliveries is an array of Mail::Message's. From the mail documentation:
# mail['from'] = 'mikel#test.lindsaar.net'
# mail[:to] = 'you#test.lindsaar.net'
# mail.subject 'This is a test email'
# mail.body = 'This is a body'
#
# mail.to_s #=> "From: mikel#test.lindsaar.net\r\nTo: you#...
From that it should be easy to test your mail's in an integration
mail = ActionMailer::Base.deliveries.last
assert_equal 'mikel#test.lindsaar.net', mail['from'].to_s
assert_equal 'you#test.lindsaar.net', mail['to'].to_s
When using the ActionMailer during tests, all mails are put in a big array called deliveries. What you basically are doing (and is sufficient mostly) is checking if emails are present in the array.
But if you want to specifically check for a certain email, you have to know what is actually stored in the array. Luckily the emails themselves are stored, thus you are able to iterate through the array and check each email.
See ActionMailer::Base to see what configuration methods are available, which you can use to determine what emails are present in the array. Some of the most suitable methods for your case probably are
recipients: Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the To: header.
subject: The subject of your email. Sets the Subject: header.
Using current Rspec syntax, I ended up using the following:
last_email = ActionMailer::Base.deliveries.last
expect(last_email.to).to eq ['test#example.com']
expect(last_email.subject).to have_content 'Welcome'
The context of my test was a feature spec where I wanted to make sure a welcome email was sent to a user after signing up.
As of 2020 (Rails 6 era, probably introduced earlier) you can do the following:
(using a SystemTest example) TL;DR: use assert_emails from ActionMailer::TestHelper and ActionMailer::Base.deliveries.last to access the mail itself.
require "application_system_test_case"
require 'test_helper'
require 'action_mailer/test_helper'
class ContactTest < ApplicationSystemTestCase
include ActionMailer::TestHelper
test "Send mail via contact form on landing page" do
visit root_url
fill_in "Message", with: 'message text'
# Asserting a mail is sent
assert_emails 1 do
click_on "Send"
end
# Asserting stuff within that mail
last_email = ActionMailer::Base.deliveries.last
assert_equal ['whatever'], last_email.reply_to
assert_equal "contact", last_email.subject
assert_match /Mail from someone/, last_email.body.to_s
end
end
Official doc:
ActionMailer Guide/Testing
Testing Guide/ActionMailer
Note
Instead of manually checking the content of the mail as in the system test above, you can also test whether a specific mailer action was used, like this:
assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
And some other handy assertion, see https://api.rubyonrails.org/v6.0.3.2/classes/ActionMailer/TestHelper.html#method-i-assert_emails .
The test framework shoulda has an excellent helper which lets you assert certain conditions about an email that was sent. Yes, you could do it yourself with ActionMailer.deliveries, but shoulda makes it all one neat little block
A little late, but it may help others:
You could use Email-spec, a collection of Rspec/Minitest matchers and Cucumber steps.
Here is the best way I've found to do it.
1) Include the action mailer callbacks plugin like this:
script/plugin install git://github.com/AnthonyCaliendo/action_mailer_callbacks.git
I don't really use the plugin's main features, but it does provide the nice functionality of being able to figure out which method was used to send an email.
2) Now you can put some methods in your test_helper.rb like this:
def assert_sent(method_name)
assert sent_num_times(method_name) > 0
end
def assert_not_sent(method_name)
assert sent_num_times(method_name) == 0
end
def assert_sent_once(method_name)
assert sent_num_times(method_name) == 1
end
def sent_num_times(method_name)
count = 0
#emails.each do |email|
count += 1 if method_name == email.instance_variable_get("#method_name")
end
count
end
3) Now you can write sweet tests like this:
require 'test_helper'
class MailingTest < ActionController::IntegrationTest
def setup
#emails = ActionMailer::Base.deliveries
#emails.clear
end
test "should send a mailing" do
assert_difference "Mailing.count", 1 do
feeds(:feed1).generate_mailing
end
assert_sent_once "broadcast"
assert_not_sent "failed_mailing"
end
end
Here "broadcast" and "mailing_failed" are the names of the methods in my ActionMailer::Base class. These are the ones you normally use by calling Mailer.deliver_broadcast(some_data) or Mailer.deliver_failed_mailing(some_data) etc. That's it!

Resources