38

I have a need to use two different smtp servers in a Rails application. It appears that the way ActionMailer is constructed, it is not possible to have different smtp_settings for a subclass. I could reload the smtp settings for each mailer class whenever a message is being sent, but that messes up the ExceptionNotifier plugin which is outside my control (unless I mess with it too). Does anyone have a solution/plugin for something like this?

Ideally I would like to have

class UserMailer < ActionMailer::Base; end

and then set in environment.rb

ActionMailer::Base.smtp_settings = standard_smtp_settings
UserMailer.smtp_settings = user_smtp_settings

Thus, most of my mailers including ExceptionNotifier would pickup the default settings, but the UserMailer would use a paid relay service.

mikej
  • 65,295
  • 17
  • 152
  • 131
  • For more reference, here is a pull request which was finally only merged onto Rails 4, as Rails 3.2 was not accepting new features. https://github.com/rails/rails/pull/7397 – polmiro Aug 14 '13 at 18:17
  • See @wnm's answer for the Rails 4 way, with a link to the docs. It's quite simple now. http://stackoverflow.com/a/34948143/456791 – Bek Sep 08 '16 at 14:17

11 Answers11

25
class UserMailer < ActionMailer::Base
  def welcome_email(user, company)
    @user = user
    @url  = user_url(@user)
    delivery_options = { user_name: company.smtp_user,
                         password: company.smtp_password,
                         address: company.smtp_host }
    mail(to: @user.email,
         subject: "Please see the Terms and Conditions attached",
         delivery_method_options: delivery_options)
  end
end

Rails 4 allows for dynamic delivery options. The above code is straight from the action mailer basics guide, which you can find here: http://guides.rubyonrails.org/v4.0/action_mailer_basics.html#sending-emails-with-dynamic-delivery-options

With this, it is possible to have different smtp settings for every email you send, or, like in your use case for different sub classes like UserMailer, OtherMailer etc.

wnm
  • 1,349
  • 13
  • 12
  • 1
    The best answer. Works perfectly in Rails 4 :D – Abel Apr 04 '17 at 04:42
  • Seems like using a callback is also an accepted option... https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-callbacks: "Using an after_action callback also enables you to override delivery method settings by updating mail.delivery_method.settings." May also be cleaner in a real-world scenario given your delivery settings are most likely a function of the environment they are being used in – colemerrick Aug 25 '21 at 20:15
20

Based on the Oreilly article, I came up with the solution I wrote about here: http://transfs.com/devblog/2009/12/03/custom-smtp-settings-for-a-specific-actionmailer-subclass

Here's the relevant code:

class MailerWithCustomSmtp < ActionMailer::Base
  SMTP_SETTINGS = {
    :address => "smtp.gmail.com",
    :port => 587,
    :authentication => :plain,
    :user_name => "custom_account@transfs.com",
    :password => 'password',
  }

  def awesome_email(bidder, options={})
     with_custom_smtp_settings do
        subject       'Awesome Email D00D!'
        recipients    'someone@test.com'
        from          'custom_reply_to@transfs.com'
        body          'Hope this works...'
     end
  end

  # Override the deliver! method so that we can reset our custom smtp server settings
  def deliver!(mail = @mail)
    out = super
    reset_smtp_settings if @_temp_smtp_settings
    out
  end

  private

  def with_custom_smtp_settings(&block)
    @_temp_smtp_settings = @@smtp_settings
    @@smtp_settings = SMTP_SETTINGS
    yield
  end

  def reset_smtp_settings
    @@smtp_settings = @_temp_smtp_settings
    @_temp_smtp_settings = nil
  end
end
jkrall
  • 536
  • 3
  • 10
  • Thanks for the reply - this looks like a good solution (thought I don't have enough reputation to vote it up). I ended up switching from ExceptionNotifier to HopToad which made the O'Reilly solution again posible. –  Dec 05 '09 at 21:35
  • I had to add :domain => 'localhost:3000', :authentication => :plain, :enable_starttls_auto => true, to get it to work but this is the best solution I have found – thenengah Jun 19 '10 at 09:59
  • and of course change localhost:3000 to the appropriate domain in production – thenengah Jun 19 '10 at 09:59
  • But this seems to suffer from the concurrency problems described in @jcalvert's answer to [How to send emails with multiple, dynamic smtp using Actionmailer/Ruby on Rails](http://stackoverflow.com/questions/2662759/how-to-send-emails-with-multiple-dynamic-smtp-using-actionmailer-ruby-on-rails). – Hew Wolff Dec 03 '13 at 23:05
19

Solution for Rails 4.2+:

config/secrets.yml:

production:
  gmail_smtp:
    :authentication: "plain"
    :address: "smtp.gmail.com"
    :port: 587
    :domain: "zzz.com"
    :user_name: "zzz@zzz.com"
    :password: "zzz"
    :enable_starttls_auto: true
  mandrill_smtp:
    :authentication: "plain"
    :address: "smtp.mandrillapp.com"
    :port: 587
    :domain: "zzz.com"
    :user_name: "zzz@zzz.com"
    :password: "zzz"
    :enable_starttls_auto: true
  mailgun_smtp:
    :authentication: "plain"
    :address: "smtp.mailgun.org"
    :port: 587
    :domain: "zzz.com"
    :user_name: "zzz@zzz.com"
    :password: "zzz"
    :enable_starttls_auto: true

config/environments/production.rb:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = Rails.application.secrets.gmail_smtp

app/mailers/application_mailer.rb:

class ApplicationMailer < ActionMailer::Base
  default from: '"ZZZ" <zzz@zzz.com>'

  private

  def gmail_delivery
    mail.delivery_method.settings = Rails.application.secrets.gmail_smtp
  end

  def mandrill_delivery
    mail.delivery_method.settings = Rails.application.secrets.mandrill_smtp
  end

  def mailgun_delivery
    mail.delivery_method.settings = Rails.application.secrets.mailgun_smtp
  end
end

app/mailers/user_mailer.rb:

class UserMailer < ApplicationMailer
  # after_action :gmail_delivery, only: [:notify_user]
  after_action :mandrill_delivery, only: [:newsletter]
  after_action :mailgun_delivery, only: [:newsletter2]

  def newsletter(user_id); '...' end # this will be sent through mandrill smtp
  def newsletter2(user_id); '...' end # this will be sent through mailgun smtp
  def notify_user(user_id); '...' end # this will be sent through gmail smtp
end
Mayur Shah
  • 3,344
  • 1
  • 22
  • 41
Lev Lukomsky
  • 6,346
  • 4
  • 34
  • 24
10

For anybody approaching this issue with later versions (3+) of Rails, try this

http://guides.rubyonrails.org/action_mailer_basics.html#sending-emails-with-dynamic-delivery-options

bodacious
  • 6,608
  • 9
  • 45
  • 74
6

Tried to use jkrall's option with Rails 3.2.1 but for some reason it wouldn't override default configuration, but doing:

MyMailer.my_email.delivery_method.settings.merge!(SMTP_SETTINGS).deliver

Similar to http://www.scottw.com/multiple-smtp-servers-with-action-mailer, made it work.

TuteC
  • 4,342
  • 30
  • 40
  • 1
    fyi - merge! returns a hash which doesn't have a deliver method. – Akshay Rawat Mar 04 '13 at 19:00
  • The blog post mentioned has had its URL changed and can now be found at http://dev.scottw.com/multiple-smtp-servers-with-action-mailer. – Austin Ziegler Jun 06 '13 at 17:38
  • 1
    Related - http://stackoverflow.com/a/6413630/242426. There's also a pull request that was merged into Rails 4+ to add support for this out of the box - https://github.com/rails/rails/pull/7397 – plainjimbo Jun 24 '13 at 18:22
2

Rails-2.3.*

# app/models/user_mailer.rb
class UserMailer < ActionMailer::Base
  def self.smtp_settings
    USER_MAILER_SMTP_SETTINGS
  end

  def spam(user)
    recipients user.mail
    from 'spam@example.com'
    subject 'Enlarge whatever!'
    body :user => user
    content_type 'text/html'
  end
end

# config/environment.rb
ActionMailer::Base.smtp_settings = standard_smtp_settings
USER_MAILER_SMTP_SETTINGS = user_smtp_settings

# From console or whatever...
UserMailer.deliver_spam(user)
Kostia
  • 150
  • 4
0

I'm afraid it's not doable natively.
But you can trick it a bit by modifying the @@smtp_settings variable in the model.

There's an article on Oreilly which explains it pretty well even though they code is not clean at all. http://broadcast.oreilly.com/2009/03/using-multiple-smtp-accounts-w.html

Damien MATHIEU
  • 31,924
  • 13
  • 86
  • 94
  • Thanks for the reply. I saw that article, and that is what I meant by reload the smtp settings before each mail (which is what the article did in the load_settings method). This is close to a solution, but it messes up ExceptionNotifier because I cannot reload the settings for it without changing the plugin. I was hoping there for something more maintainable. –  Oct 13 '09 at 20:55
  • Unfortunately, because of the way it is implemented, it's not possible. – Damien MATHIEU Oct 14 '09 at 06:20
  • Anything's possible with enough monkey patches. – tadman Dec 22 '09 at 16:51
  • Please help get this PR merged in rails. (https://github.com/rails/rails/pull/7397). This does exactly what the OP is thinking of easily. – Aditya Sanghi Aug 30 '12 at 07:04
0

Solution for Rails 3.2:

class SomeMailer < ActionMailer::Base

  include AbstractController::Callbacks
  after_filter :set_delivery_options

  private
  def set_delivery_options
    settings = {
      :address => 'smtp.server',
      :port => 587,
      :domain => 'your_domain',
      :user_name => 'smtp_username',
      :password => 'smtp_password',
      :authentication => 'PLAIN' # or something
    }

    message.delivery_method.settings.merge!(settings)
  end
end

Solution inspired by How to send emails with multiple, dynamic smtp using Actionmailer/Ruby on Rails

Community
  • 1
  • 1
Dan Mazzini
  • 1,005
  • 10
  • 19
0

When I wanted a quick test in the console (Rails 5) I did the following:

settings = { username: "" } # etc
mailer = MyMailer.some_method
mailer.delivery_method.settings.merge!(settings)
mailer.deliver
Chris Edwards
  • 3,514
  • 2
  • 33
  • 40
0

https://github.com/AnthonyCaliendo/action_mailer_callbacks

I found this plugin helped solve the problem for me pretty easily (as in < 5 mins). I simply change the @@smtp_settings for a particular mailer in the before_deliver and then change it back to the defaults in the after_deliver. Using this approach, I only have to add the callbacks to mailers that need @@smtp_settings different than the default.

class CustomMailer < ActionMailer::Base

  before_deliver do |mail|
    self.smtp_settings = custom_settings
  end

  after_deliver do |mail|
    self.smtp_settings = default_settings
  end

  def some_message
    subject "blah"
    recipients "blah@blah.com"
    from "ruby.ninja@ninjaness.com"
    body "You can haz Ninja rb skillz!"
    attachment some_doc
  end

end
bkidd
  • 560
  • 7
  • 7
0

Here's another solution, which, while it looks ridiculous, I think is a little bit cleaner and easier to reuse in different AM::Base classes:

    module FTTUtilities
      module ActionMailer
        module ClassMethods
          def smtp_settings
            dict = YAML.load_file(RAILS_ROOT + "/config/custom_mailers.yml")[self.name.underscore]
            @custom_smtp_settings ||= HashWithIndifferentAccess.new(dict)
          end
        end

        module InstanceMethods
          def smtp_settings
            self.class.smtp_settings
          end
        end
      end
    end

example Mailer:

    class CustomMailer < ActionMailer::Base
        extend FTTUtilites::ActionMailer::ClassMethods
        include FTTUtilites::ActionMailer::InstanceMethods
    end
narsk
  • 581
  • 6
  • 9