62

I'm trying to send multiple emails based on a boolean value in my database. The app is a simple scheduling app and user can mark their shift as "replacement_needed" and this should send out emails to all the users who've requested to receive these emails. Trouble is, it only every seems to send to one email. Here's my current code:

 def request_replacement(shift)
      @shift = shift
      @user = shift.user
      @recipients = User.where(:replacement_emails => true).all
      @url  = root_url
      @recipients.each do |r|
        @name = r.fname
        mail(:to => r.email,
           :subject => "A replacement clerk has been requested")
      end
  end
Slick23
  • 5,827
  • 10
  • 41
  • 72
  • 7
    The call to #mail doesn't actually send the email, it just creates the mail object. Calling #deliver on that mail object sends it. Hence, only the last one created is sent when you call #deliver (presumably in another bit of code). – Alex Ghiculescu Feb 26 '13 at 07:51

5 Answers5

82

In the Rails guides (Action Mailer Basics) it says the following regarding multiple emails:

The list of emails can be an array of email addresses or a single string with the addresses separated by commas.

So both "test1@gmail.com, test1@gmail.com" and ["test1@gmail.com", "test1@gmail.com"] should work.

See more at: http://guides.rubyonrails.org/action_mailer_basics.html

raoul_dev
  • 1,396
  • 12
  • 16
  • 1
    This is the best solution and it links to the official documentation. If you want to hide the recipients use bcc. – Toshe Jan 19 '21 at 14:57
80

You can just send one email for multiple recipients like this.

def request_replacement(shift)
  @shift = shift
  @user = shift.user
  @recipients = User.where(:replacement_emails => true)
  @url  = root_url
  emails = @recipients.collect(&:email).join(",")
  mail(:to => emails, :subject => "A replacement clerk has been requested")
end

This will take all your @recipients email addresses and join them with ,. I think you can also pass an array to the :to key but not sure.

The only problem is you won't be able to use @name in your template. :(

Chris Ledet
  • 11,458
  • 7
  • 39
  • 47
  • 10
    Yeah, but I really don't want to expose the email addresses of every user ... I think I found a solution by moving the .each block in to the model and calling deliver from there. – Slick23 Sep 15 '11 at 20:57
  • 16
    True, if that's the case. You can use `bcc` field. – Chris Ledet Sep 15 '11 at 20:59
  • 1
    FWIW, this doesn't seem to work on Amazon SES, where they are expecting commas as the delimiters (though it works when you use comma instead of semicolon). – Marc Talbot May 31 '12 at 22:30
  • 7
    Comma is the correct delimiter, not a semicolon. AFAIK only Outlook uses semicon, and it translates it to commas when it generates the final email. – Urkle Aug 18 '12 at 17:27
  • 3
    `bcc` is a way to reach `don't want to expose the email addresses of every user`, but since this will hide receivers' address, many mail services will detect the mail as a spam. – Veck Hsiao Jun 08 '17 at 17:48
63

I have the same problem. I don't know what is the deal is. I sidestep it by:

instead of calling

Mailer.request_replacement(shift).deliver 

from my controller,

I'd define a class method on the mailer, and call that. That method would then iterate through the list and call deliver "n" times. That seems to work:

class Mailer

   def self.send_replacement_request(shift)
     @recipients = ...
     @recipients.each do |recipient|
       request_replacement(recipient, shift).deliver
     end
   end

   def request_replacement(recipient, shift)
     ...
     mail(...)
   end
end

and from the controller, call

Mailer.send_replacement_request(shift)
peterh
  • 11,875
  • 18
  • 85
  • 108
noli
  • 15,927
  • 8
  • 46
  • 62
7

To prevent each recipient from seeing the other email addresses:

@recipients.each{ |recipient| Mailer.request_replacement(recipient, shift).deliver }
zolter
  • 7,070
  • 3
  • 37
  • 51
  • You could also add them as `bcc`, like this: `mail(:to =>"your_email@domain.com", :subject => "A replacement clerk has been requested", :bcc => @recipients.map(&:email).join(","))` – cgenco Sep 08 '16 at 18:46
  • I just want to point out that the `join` in your comment is not needed. action mailer can take arrays of emails – TheRealMrCrowley Sep 05 '17 at 17:24
3

I'm using Rails 5 and I have the same situation, the email was sent only to the last recipient but also it was sent just as plain text not as HTML email.

After trying some advices, I ended up fixing it in this way:

The mailer:

class BrochureMailer < ApplicationMailer
    default from: "info@site.com"

    def newsletter(sponsor, brochures_list)
        @sponsor = sponsor
        @brochures = brochures_list

        mail(
            to: @sponsor.email,
            subject: "Interesting subject!"
        )
    end
end

The controller where the mailer is invoked:

class Admin::DashboardController < Admin::BaseController
    def send_newsletter
        sponsors = params[:sponsor_ids]
        brochures = params[:brochure_ids]

        sponsors = Sponsor.where(id: sponsors)
        brochures = Brochure.where(id: brochures).to_a

        # Send Newsletter email to the given Sponsors
        sponsors.each do |sponsor|
            BrochureMailer.newsletter(sponsor, brochures).deliver_later
        end

        redirect_back(fallback_location: admin_root_path, success: 'Newsletter sent!')
    end
end

And in the view, something like this:

<% @brochures.each do |brochure| %>
    <table width="280" border="0" cellpadding="0" cellspacing="0" align="left" valign="top" class="floater">
        <tr>
            <td align="center" valign="top">
                <a target="_blank" href="<%= brochure_url(brochure) %>">
                    <img border="0" vspace="0" hspace="0" src="<%= brochure.image.blank? ? 'default.png' : brochure.image.url(public: true) %>" width="250" height="142">
                    <b><%= brochure.title %></b>
                </a>
                <br>
                <%= brochure.description.truncate(60) %>
            </td>
        </tr>
    </table>
<% end %>

And it works like a charm! I'm not sure if this is the correct way or the most optimal way to go but just consider it as a second possibility.

I hope it could be useful for somebody else.

alexventuraio
  • 8,126
  • 2
  • 30
  • 35