34
votes

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.

11
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. github.com/rails/rails/pull/7397polmiro
See @wnm's answer for the Rails 4 way, with a link to the docs. It's quite simple now. stackoverflow.com/a/34948143/456791Bek

11 Answers

21
votes

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 => "[email protected]",
    :password => 'password',
  }

  def awesome_email(bidder, options={})
     with_custom_smtp_settings do
        subject       'Awesome Email D00D!'
        recipients    '[email protected]'
        from          '[email protected]'
        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
20
votes
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.

16
votes

Solution for Rails 4.2+:

config/secrets.yml:

production:
  gmail_smtp:
    :authentication: "plain"
    :address: "smtp.gmail.com"
    :port: 587
    :domain: "zzz.com"
    :user_name: "[email protected]"
    :password: "zzz"
    :enable_starttls_auto: true
  mandrill_smtp:
    :authentication: "plain"
    :address: "smtp.mandrillapp.com"
    :port: 587
    :domain: "zzz.com"
    :user_name: "[email protected]"
    :password: "zzz"
    :enable_starttls_auto: true
  mailgun_smtp:
    :authentication: "plain"
    :address: "smtp.mailgun.org"
    :port: 587
    :domain: "zzz.com"
    :user_name: "[email protected]"
    :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" <[email protected]>'

  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
10
votes
6
votes

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.

2
votes

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 '[email protected]'
    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)
0
votes

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

0
votes

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 "[email protected]"
    from "[email protected]"
    body "You can haz Ninja rb skillz!"
    attachment some_doc
  end

end
0
votes

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
0
votes

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

0
votes

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