使用多个SMTP服务器的Rails ActionMailer

38
我需要在Rails应用程序中使用两个不同的SMTP服务器。看起来,根据ActionMailer的构造方式,无法为子类设置不同的smtp_settings。我可以在每次发送邮件时重新加载每个电子邮件程序类的smtp设置,但这会影响到我不能控制的ExceptionNotifier插件(除非我也对其进行修改)。有人有像这样的解决方案/插件吗?
理想情况下,我希望拥有:
class UserMailer < ActionMailer::Base; end

然后在environment.rb中进行设置

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

因此,我的大多数邮件发送程序,包括ExceptionNotifier,会使用默认设置,但是UserMailer将使用付费中继服务。


更多参考资料,请查看此 pull request,该请求最终仅合并到 Rails 4 中,因为 Rails 3.2 不接受新功能。https://github.com/rails/rails/pull/7397 - polmiro
参见 @wnm 的回答,其中包含指向文档的链接,这是 Rails 4 的正确方式。现在非常简单。https://dev59.com/xnI_5IYBdhLWcg3wBuT3#34948143 - Bek
11个回答

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允许动态设置投递选项。以上代码来自于Action Mailer基础指南,您可以在此处找到:http://guides.rubyonrails.org/v4.0/action_mailer_basics.html#sending-emails-with-dynamic-delivery-options
因此,您可以为每封电子邮件发送不同的SMTP设置,或者针对不同的子类(例如UserMailer,OtherMailer等)设置不同的SMTP设置。

1
最佳答案。在Rails 4中完美运行:D - Abel
似乎使用回调也是一种可接受的选项...https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-callbacks: "使用after_action回调还可以通过更新mail.delivery_method.settings来覆盖传递方法设置。" 在现实世界的情况下,这样做可能更加干净,因为您的传递设置很可能是它们所在环境的函数。 - colemerrick

20
根据《O'Reilly》的文章,我得出了我在这里写的解决方案:http://transfs.com/devblog/2009/12/03/custom-smtp-settings-for-a-specific-actionmailer-subclass 以下是相关代码:
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

谢谢回复,这看起来是一个不错的解决方案(虽然我没有足够的声誉来投票支持它)。最终我改用了HopToad而不是ExceptionNotifier,这使得O'Reilly的解决方案再次成为可能。 - user189053
我不得不添加 :domain => 'localhost:3000', :authentication => :plain, :enable_starttls_auto => true,才能让它正常工作,但这是我找到的最好的解决方案。 - thenengah
当然,在生产环境中,请将localhost:3000更改为适当的域名。 - thenengah
但是这似乎遭受了并发问题,正如@jcalvert在如何使用Actionmailer / Ruby on Rails发送具有多个动态SMTP的电子邮件的答案中所述。 - Hew Wolff

19
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

10

1
这是最干净的解决方案! - hcarreras

6

1
提示:merge!返回一个哈希表,该哈希表没有deliver方法。 - Akshay Rawat
博客文章提到的URL已更改,现在可以在http://dev.scottw.com/multiple-smtp-servers-with-action-mailer找到。 - Austin Ziegler
1
相关 - https://dev59.com/a3E85IYBdhLWcg3wr1ge#6413630。此外,还有一个拉取请求已经合并到Rails 4+中,以支持开箱即用 - https://github.com/rails/rails/pull/7397 - plainjimbo

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)

0
这里有另外一种解决方案,看起来可能有些荒谬,但我认为它更加简洁,且在不同的 AM::Base 类中重复使用也更容易些。
    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

示例邮件程序:

    class CustomMailer < ActionMailer::Base
        extend FTTUtilites::ActionMailer::ClassMethods
        include FTTUtilites::ActionMailer::InstanceMethods
    end

0

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

灵感来自于如何使用Actionmailer/Ruby on Rails发送多个动态SMTP邮件


0

https://github.com/AnthonyCaliendo/action_mailer_callbacks

我发现这个插件帮助我很容易地解决了问题(不到5分钟)。我只需在before_deliver中更改特定邮件发送器的@@smtp_settings,然后在after_deliver中将其更改回默认值。使用这种方法,我只需要将回调添加到需要与默认值不同的@@smtp_settings的邮件发送器中。

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

0

谢谢回复。我看到了那篇文章,这就是我所说的在每封邮件之前重新加载smtp设置(这就是该文章在load_settings方法中所做的)。这已经接近一个解决方案,但它会破坏ExceptionNotifier,因为我无法重新加载其设置而不更改插件。我希望有一种更可维护的解决方案。 - user189053
不幸的是,由于它的实现方式,这是不可能的。 - Damien MATHIEU
只要有足够的猴子补丁,任何事情都是可能的。 - tadman
请帮助将此PR合并到Rails中 (https://github.com/rails/rails/pull/7397)。它可以轻松地实现OP想要的功能。 - Aditya Sanghi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接