Rails send e-mail in a loop - ruby-on-rails

I have the snippet below running in my Rails app, but it's not iterating properly:
task.users.each do |user|
mail(to: user.email
from: "Company <task." + user.id + "#mail.company.com",
subject: "Update on " + user.assignment)
end
If there's 3 users, it only sends out one e-mail. I've verified that there aren't any errors preventing it from continuing.
I can't simply pass an array of e-mails to the to option because I need to use the user's data in each iteration.
Can this be done like the above?

You need to call the .deliver_now method.
mail(to: user.email
from: "Company <task." + user.id + "#mail.company.com",
subject: "Update on " + user.assignment).deliver_now
I have run into an issue where this kind of delivery doesn't work, possibly because the mail was falling out of scope.
So you may need to do something like this:
class SendWeeklySummary
def run
User.find_each do |user|
UserMailer.weekly_summary(user).deliver_now
end
end
end
Rails Guides docs - see section 2.1.4

Related

How to Skip Devise Confirmation email using SeedFu gem

I'm using the Seed-Fu gem to populate my database. So, I have this in one of my seed files:
User.seed_once(:email) do |user|
user.id = 1
user.email = "test#test.com"
user.first_name = "First"
user.last_name = "Last"
user.confirmation_sent_at = Time.zone.now - 1.hour
user.confirmed_at = Time.zone.now
user.roles = [root,staff]
user.permissions = Permission.all
end
From what I've read, that should prevent Devise from sending a confirmation email. However, it is not, so (since I'm using the Letter Opener gem) my browser is being flooded with confirmation emails. Anyone know why this is happening and how I can convince Devise to not send these emails whilst I'm seeding?
SOLUTION:
Based on anothermh's answer below, I added Devise::Mailer.perform_deliveries = false to the top of this fixture file. Then, I found my final fixture file and added Devise::Mailer.perform_deliveries = true to the end of that to make sure emails would be sent when actually using the app. Thank you so much, folks!
You just need to call the confirm method on user object. See the below code
user.confirm
So in your case the code will be like
User.seed_once(:email) do |user|
user.id = 1
user.email = "test#test.com"
user.first_name = "First"
user.last_name = "Last"
user.confirm
user.roles = [root,staff]
user.permissions = Permission.all
end
You can turn off email sends from Devise in tests. Specifically, you'll need to have this set somewhere:
Devise::Mailer.delivery_method = :test
Devise::Mailer.perform_deliveries = false
For example, if you're doing this with rspec then you would likely put it into spec/rails_helper.rb.

Rails 4 - How to send newsletter-like emails with delayed_job?

I want to send a summary of our new lists to our users every morning. What's the best approach to do that with Ruby On Rails 4, ActiveRecord (using SendGrid) and Delayed Job?
I am currently doing it this way:
In controller:
def yesterday_listings_for_users
yesterday_listings = Listings.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1)
if yesterday_listings.count > 0
NotificationMailer.delay.yesterday_listings_for_users_notification
end
render :nothing => true
end
And then in the mailer:
def yesterday_listings_for_users_notification
#listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today-1)
mail(to: 'myemail#gmail.com', subject: "Latest Listings", from: 'no-reply#mywebsite.com')
end
With using a CRON job, this sends me the report every morning on my email address. I have a few hundreds of users in the database and I would like to send them this email as well.
How to do that? I am wondering about something like this:
def yesterday_listings_for_users_notification
#listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today-1)
Users.all.each do |user|
mail(to: user.email, subject: "Latest Listings", from: 'no-reply#mywebsite.com')
end
end
However, is looping through hundreds of records in database and sending hundreds of emails in a delayed mailer method recommened (or appropriate)?
Is there a better way to do that?
Thank you in advance!
I usually prefer to use Sidekiq along with Sidetiq but if you want to use delayed_job I would advice you to use the whenever gem for simplicity.
Whenever is a Ruby gem that provides a clear syntax for writing and
deploying cron jobs.
Add gem 'whenever' to your gemfile
run the command wheneverize . which will generate a file config/schedule.rb
In your config/schedule.rb do the following.
every 1.day, :at => '11:30 am' do
runner "User.delay.send_daily_newsletter"
end
In your user.rb define the method send_daily_newsletter and use find_each instead of all.each (batches)
def self.send_daily_newsletter
listings = Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1).select(:title).to_json
User.select(:id, :email).find_each do |u|
NotificationMailer.delay.send_daily_newsletter(u.email, listings)
end
end
In your notification_mailer.rb define send_daily_newletter
def send_daily_newsletter(user_email, listings)
#listings = listings
mail(to: user_email, subject: "Latest Listings", from: 'no-reply#mywebsite.com')
end
This way you will have one delayed job to get all users and send each email using a separate worker which is the most optimal way to do this task.
Note: Do not forget to change the methods for listings in your view
from, for example, listing.title to listing[:title] since we are
passing the listings as json.
If you do not want to pass them as json every time to every delayed
task just cache the listings in Rails.cache and clear it
after you finish sending.
EDIT:
If you would like to use the cache method since you ran into a problem in the delayed_job gem, edit your send_daily_newsletter method in your mailer. (That's is why I would go to redis-based Sidekiq rather than mysql-based delayed_job.
def send_daily_newsletter(user_email)
#listings = Rails.cache.fetch('today_listings') { Listing.where('status = "0" AND (DATE(created_at) = ?)', Date.today - 1) }
mail(to: user_email, subject: "Latest Listings", from: 'no-reply#mywebsite.com')
end
and in your user.rb
def self.send_daily_newsletter
User.select(:id, :email).find_each do |u|
NotificationMailer.delay.send_daily_newsletter(u.email)
end
Rails.cache.clear('today_listings')
end
Good luck. I have been doing these email newsletters for a while now and they are truly pain :D

Rails 4.1 -> 4.2 has caused an after_create callback that sends an e-mail to stop working

In my Note model (note.rb) I have the snippet below that is being triggered by an Active Record callback:
def creation_email
case self.notable_type
when 'Application'
NoteMailer.run('application', self)
when 'Appointment'
NoteMailer.run('appointment', self)
end
self.inbounded = 1
self.save
end
In the NoteMailer I have another switch statement to send different e-mail templates based on the polymorphic notable_type. Here is that snippet:
def run(type, note)
#note = note
case type
when 'application'
app = Application.find(#note.notable_id)
app.users.each do |user|
if user.id != #note.user.id && user.email
mail(to: user.email,
from: #note.user.name + " <application." + app.id.to_s + "." + user.id.to_s + "#mail.mysecretdomain.com>",
subject: "my secret subject",
template_name: "application.html.erb").deliver_now
end
end
when 'appointment'
a = Appointment.find(#note.notable_id)
a.users.each do |user|
if user.id != #note.user.id && user.email
mail(to: user.email,
from: note.user.name + " <appointment." + a.id.to_s + "." + user.id.to_s + "#mail.mysecretdomain.com>",
subject: "my secret subject",
template_name: "appointment.html.erb").deliver_now
end
end
end
end
Since upgrading to Rails 4.2 from Rails 4.1, the e-mails are not sending out as they used to.
What could be causing this problem? I did change mail(..).deliver to mail(..).deliver_now.
I've tried adding puts "test" in the run method at the top, and it does not put the string to the console. No errors at all are thrown
The problem is run is never called. You need to call deliver_now in the proper place.
Try this :
def creation_email
case self.notable_type
when 'Application'
NoteMailer.run('application', self).deliver_now
when 'Appointment'
NoteMailer.run('appointment', self).deliver_now
end
self.inbounded = 1
self.save
end
Instead of calling deliver_now in your Mailer.
See http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2

ROR sending email based on account number

We're using Ruby 1.9 and Rails 3.2 We use AS/400 for our database. We've tried using Active Record for this before, and it doesn't want to work because of our versions of Ruby and Rails being older combined with getting it to connect with the 400.
We have an online ordering site that you have to have an account set up to access. Depending on what type of account you are set up as, you might have to have your order approved by someone. I.e. if I am a drop ship account, my distributor has to approve what I'm ordering. The way it had been set up, the distributor wasn't getting any kind of approval email.
We've been trying the line of code below in console, and have it working. If we enter an account number instead of leaving it blank, it returns a list of the email addresses that would be getting the approval email. Fantastic! If it's left blank as shown below, it returns a blank array. Makes sense, we haven't logged in, so it doesn't know our account number yet. When the user logs in, their account number should automatically be substituted in.
Contact.find_by_sql ["SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = ‘’”]
However, when we add that code snippet into the order.rb file, it returns an error:
wrong number of arguments (1 for 4)
Clearly, the way we have the line set up isn't right:
Mailer.deliver_order_distributor_approval_email('Contact.find_by_sql (SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"')
It's just getting confusing... we seem to be going in circles with the errors. We try to fix it, but then we just get new errors that still have to do with that line.
The deliver_order_distributor_approval_email method is in the mailer.rb file below:
class Mailer < ActionMailer::Base
##################################################################################
##################################################################################
# NOTE: in all cases, use sanitize_email to ensure nothing goes out by accident. #
##################################################################################
##################################################################################
def order_confirmation_email(recipient_email, from_email, subject, email_details)
recipient_email = Mailer.sanitize_email(recipient_email)
# Get the template of the email body from the database and then perform all replacements.
email_body = Setting.first.email_order_body
email_details.each {|key, value| email_body.gsub!("##" + key.upcase.gsub("_"," ") + "##", value)}
recipients recipient_email
from from_email
subject subject
bcc Setting.first[:email_admin_to]
part :content_type => "text/html",
:body => email_body
#f=File.open("/var/www/onlineordering.example.com/log/debugger.txt")
#f.puts get_email = $get_email
#f.close
end
def order_coastal_notify_email(from_email, subject, email_details)
# Get the template of the email body from the database and then perform all replacements.
email_body = Setting.first.email_order_coastal_notify
email_details.each {|key, value| email_body.gsub!("##" + key.upcase.gsub("_"," ") + "##", value)}
recipients Setting.first[:email_order_placed_to]
from from_email
subject subject
bcc Setting.first[:email_admin_to]
part :content_type => "text/html",
:body => email_body
end
def order_distributor_approval_email(recipient_email, from_email, subject, email_details)
recipient_email = Mailer.sanitize_email(recipient_email)
# Get the template of the email body from the database and then perform all replacements.
# We run the attachment and the email body through replacement tags.
email_body = Setting.first.email_order_attachment_body
email_attachment_body = Setting.first.email_order_attachment
email_details.each {|key, value| email_body.gsub!("##" + key.upcase.gsub("_"," ") + "##", value)}
email_details.each {|key, value| email_attachment_body.gsub!("##" + key.upcase.gsub("_"," ") + "##", value)}
# If their email is blank, we'll send it to admin.
recipients recipient_email.blank? ? Setting.first[:email_admin_to] : recipient_email
from from_email
subject subject
bcc Setting.first[:email_order_placed_to]
part :content_type => "text/html",
:body => email_body
attachment "application/pdf" do |a|
a.body = WickedPdf.new.pdf_from_string(email_attachment_body)
a.filename = "Drop Ship Approval Form.pdf"
end
end
def order_again_reminder_email(name, recipient_email, from_email, subject)
recipient_email = Mailer.sanitize_email(recipient_email)
recipients recipient_email
from from_email
bcc Setting.first[:email_admin_to]
subject subject
body :content => Setting.first.email_reminder_body.gsub!(/##NAME##/, name.to_s.titleize)
end
def forgot_password_email(recipient_email, from_email, subject, password)
recipient_email = Mailer.sanitize_email(recipient_email)
recipients recipient_email
from from_email
bcc Setting.first[:email_admin_to]
subject subject
body :password => password
end
def register_email(recipient_email, from_email, subject, params)
recipient_email = Mailer.sanitize_email(recipient_email)
recipients recipient_email
from from_email
bcc Setting.first[:email_admin_to]
subject subject
body :params => params
end
private
def self.sanitize_email(recipient_email)
# Comma separate multiple email addresses.
case ENV['RAILS_ENV']
when 'production'
recipient_email = recipient_email
when 'development'
recipient_email = "John Doe<john#example.com>"
else
# This is really the production, since they don't have a true production server
# should resolve to "dev".
recipient_email = recipient_email
end
recipient_email
end
end
If the order.rb file would be helpful, I can attach it... it's just kind of lengthy, so I didn't include it in the post. If you have any suggestions as to how to change the mailer.deliver_order_distributor_approval_email line, please let me know.. I'd really appreciate it! Thank you in advance!
Edit
Mailer.deliver_order_distributor_approval_email ('Contact.find_by_sql SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"'),"Coastal Pet Online Ordering<noreply#coastalpet.com>", "Order Confirmation-customer", email_details
With this (after some tweaking), we were able to submit an order without any errors, but we aren't getting any emails. Odd. It almost seems like we might be missing some mailer calls or something?
Edit
After modifying the script suggested a little, we came up with this...
target_email = Contact.find_by_sql ["SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = ''"]
Mailer.deliver_order_confirmation_email(target_email, "Coastal Pet Online Ordering<noreply#coastalpet.com>", "Order Confirmation-customer", email_details)
It "works" without errors - we can log in and submit an order, but still fail to get any email.
Oddly enough, that snippet returns the error below when ran through the console.
ActiveRecord::StatementInvalid: 37000 (-10) [IBM][iSeries Access ODBC Driver][DB 2 UDB]SQL0010 - String constant beginning ' ' not delimited.
If we put in an account number like below, it runs through and submits.. but still no email. It returns an error on the console, too.
target_email = Contact.find_by_sql ["SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 ='30153'"]
Mailer.deliver_order_confirmation_email(target_email, "Coastal Pet Online Ordering<noreply#coastalpet.com>", "Order Confirmation-customer", email_details)
Error:
NameError: undefined local variable or method `email_details' for main:Object
Ideas?
The error message you are getting:
wrong number of arguments (1 for 4)
indicates that you have called a method that expects four arguments, but you only provided one. Here's your method call:
Mailer.deliver_order_distributor_approval_email('Contact.find_by_sql (SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"')
As written, you've provided one argument: the string 'Contact.find_by_sql (SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"'.
When you call the method Mailer.deliver_order_distributor_approval_email, it eventually passes the arguments it is given to the order_distributor_approval_email method you've defined in your Mailer class. That method requires four arguments, but you're only giving it one, hence the error. You need to provide all four arguments to make the method call work.
Update:
You are passing the string 'Contact.find_by_sql SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"' as the first argument, so the mailer is trying to treat that string as an email address, which naturally isn't going to work. Presumably you want to actually call the find_by_sql method on Contact to get the real email address, in which case you'll want something like this:
target_email = Contact.find_by_sql('SELECT EMAL23 FROM WEBOEL23 WHERE ACT223 = "30153"').first
Mailer.deliver_order_distributor_approval_email(target_email, "Coastal Pet Online Ordering<noreply#coastalpet.com>", "Order Confirmation-customer", email_details)
This should perform the query and attempt to send the email to the first result. You will need to make further adjustments to send to multiple recipients or to send multiple emails.

rails increment on no error

I have a rails code that sends emails. Following is in my controller:
def create
#users = Users.find(:all)
#sub = params[:sub]
#body = params[:body]
#index = 0
#users.each {|i| index++; Notifier.deliver_notification(#users.email_address, #sub, #body, #users.unsubscribe_link);}
flash[:notice] = "Mail was sent to " + #index + " people"
end
I have the following in my Model
class Notifier < ActionMailer::Base
def notification(email, sub, content, link)
recipients email
from "my_email#example.com"
subject sub
body :content => recipient, :link => link
end
end
This all works fine. My Question is:
For example if there is an error in sending mail to one of the pople, even then my flash message will say. Mail was sent to X people
What can I do to ensure that #index gets incremented ONLY when mail is successfully sent?
The deliver_notification method should always return a TMail object regardless of success or failure. There is a raise_delivery_errors setting which will allow the mailer to raise exceptions if there's trouble, but you'll have to rescue these in your block and only increment on success.
Due to the way mail is delivered by ActionMailer, it's often the case you won't know if the message is successful or not. Email is usually queued and delivered at a point in time well beyond your method call, and most errors occur at this point due to any number of difficulties in delivery. It's only wildly malformed email addresses that will be rejected up front, or if the mail delivery mechanism is non-functional.
Edit: Added Exception Tracking
count = 0
#users.each do |user|
begin
Notifier.deliver_notification(
user.email_address,
#sub,
#body,
user.unsubscribe_link
)
count += 1
rescue => e
# Something went wrong, should probably store these and examine them, or
# at the very least use Rails.logger
end
end
flash[:notice] = "Mail was sent to #{count} people"
Your example used index++ which is not supported by Ruby. What you probably want is index += 1. You were also using the #users array directly instead of the individual elements.
You could ask ActionMailer to throw exceptions for you, and then only count those deliveries that don't result in an exception.
ActionMailer::Base.raise_delivery_errors = true
#users.each do |i|
begin
Notifier.deliver_notification(#users.email_address, #sub, #body, #users.unsubscribe_link)
index++
rescue Exception => e
# Do whatever you want with the failed deliveries here
end
end

Resources