18

I saw this post but mine is slightly different:

Rails ActionMailer with multiple SMTP servers

I am allowing the users to send mail using their own SMTP credentials so it actually does come from them.

But they will be sent from the Rails app, so that means for each user I need to send their emails using their own SMTP server.

How can I do that?

Community
  • 1
  • 1
Satchel
  • 16,414
  • 23
  • 106
  • 192
  • 1
    Please help get this PR merged in rails. (https://github.com/rails/rails/pull/7397) This does exactly what you're thinking of easily. – Aditya Sanghi Aug 30 '12 at 07:03

6 Answers6

64

Doing what is described in the other answer is not safe; you are setting class variables here, not instanced variables. If your Rails container is forking, you can do this, but now your application is depending on an implementation detail of the container. If you're not forking a new Ruby process, then you can have a race condition here.

You should have a model that is extending ActionMailer::Base, and when you call a method, it will return a Mail::Message object. That is your instance object and is where you should change your settings. The settings are also just a hash, so you can inline it.

msg = MyMailer.some_message
msg.delivery_method.settings.merge!(@user.mail_settings)
msg.deliver

Where in the above mail_settings returns some hash with appropriate keys IE

{:user_name=>username, :password=>password}
jcalvert
  • 3,628
  • 2
  • 19
  • 13
  • ActionMailer's Interceptor (delivering_email) might be a useful of this in some cases. – Akshay Rawat Jul 30 '12 at 12:02
  • 6
    Please help get this PR merged in rails. (https://github.com/rails/rails/pull/7397) This does exactly what OP is thinking of easily. – Aditya Sanghi Aug 30 '12 at 07:03
  • 8
    The pull request mentioned by @AdityaSanghi was merged into Rails 4+ but won't be patched in earlier versions. – plainjimbo Jun 24 '13 at 18:21
  • 1
    nice answer @jcalvert this was exactly my concern with the approved answer – NikoRoberts Oct 31 '13 at 15:38
  • how can you do this within the extended actionmailer model? this way the SMTP settings don't live outside an actionmailer class (your answer is specific to this question, but we don't need to pass credentials that vary by the user.) – Crashalot Feb 10 '14 at 21:19
  • can this be done in the `MyMailer` class? Like so: `self.message.delivery_method.settings.merge!(delivery_options)` at the end of the `MyMailer#some_message` ? – Oktav May 19 '14 at 11:17
14

Here is a solution that I came up with based on the previous answers and comments. This uses an ActionMailer interceptor class.

class UserMailer < ActionMailer::Base
  default from: proc{ @user.mail_settings[:from_address] }      

  class DynamicSettingsInterceptor
     def self.delivering_email(message)
       message.delivery_method.settings.merge!(@user.mail_settings)
     end
   end
   register_interceptor DynamicSettingsInterceptor
end
Mitch
  • 1,535
  • 13
  • 21
9

For Rails 3.2.x

You can include AbstractController::Callbacks in your mailer class and the do a "after_filter :set_delivery_options" inside the mailer.

The set_delivery_options method would have access to instance variables setup by you in your mailer action and you can access the mail object as "message".

class MyNailer < ActionMailer::Base
  include AbstractController::Callbacks
  after_filter :set_delivery_options

  def nail_it(user)
    @user = user
    mail :subject => "you nailed it"
  end

  private

  def set_delivery_options
    message.delivery_method.settings.merge!(@user.company.smtp_creds)
  end
end
Aditya Sanghi
  • 13,370
  • 2
  • 44
  • 50
7

Since Rails 4+ it works to give the credentials directly via the delivery_method_options parameter:

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

See Sending Emails with Dynamic Delivery Options (Rails Guides)

Gokul
  • 3,101
  • 4
  • 27
  • 45
Sandro L
  • 1,140
  • 18
  • 32
  • 1
    This should be the accepted answer! Only solution I see here that isn't a hack and it is actually documented in the official Rail guide. – haffla Dec 10 '20 at 11:20
3

Just set the ActionMailer::Base configuration values before each send action.

smtp_config = user.smtp_configuration

ActionMailer::Base.username = smtp_config.username
ActionMailer::Base.password = smtp_config.password
ActionMailer::Base.server = ..
ActionMailer::Base.port = ..
ActionMailer::Base.authentication = ..
simianarmy
  • 1,485
  • 10
  • 13
  • How do I do that? ActionMailer configs are in the config file...Creating a local variable called smtp_config and then pass the respective methods? – Satchel Apr 23 '10 at 00:48
  • 2
    SO just lost my previous answer, so trying again with a shorter note. You are just setting ActionMailer class variables in those calls. Typically they are defined in the configs, but you can change them anytime you want. In your case you want to set them before every ActionMailer child class's deliver_XXX method that you need to call. – simianarmy May 12 '10 at 15:34
  • 3
    agreed, you should re-mark jcalvert's answer as correct - the selected answer is dangerous – jpw Aug 11 '12 at 17:48
  • 1
    This is bad. Read @jcalvert's answer – dukz Nov 10 '15 at 16:47
3

in case somebody needs to set delivery method dynamically together with smtp credentials, u can use Mail::Message instance method to set delivery method together with it's variables so my addapted Aditya Sanghi version is:

class MainMailer < ActionMailer::Base
  WHATEVER_CONDITION = true # example only f.e. @ser

  include AbstractController::Callbacks
  after_filter :set_delivery_options

  private
  def set_delivery_options
    settings = {
    :address => 'smtp.mandrillapp.com', # intentionally
    :port => 587, # intentionally
    :domain => 'your_domain', #insetad of localhost.localdomain'
    :user_name => 'smtp_username',
    :password => 'smtp_password',
    :authentication => 'PLAIN' # or smthing else
}
    if WHATEVER_CONDITION
      message.delivery_method(:smtp, settings)
    end
  end
end
Community
  • 1
  • 1
morkevicius
  • 459
  • 4
  • 15
  • WHATEVER_CONDITION is a class constant, so, why do you need to use it at all? – Dan Mazzini Jun 08 '16 at 07:00
  • you don't have to do this, my point here was that u can use some condition to decide if u need to merge in(rewrite) mail delivery method or not, myself i am not using constant here, my other mailers are inheriting from MainMailer class and if some conditions are met i am using different smtp configuration, which is the main point of all of this in my case. – morkevicius Jun 08 '16 at 07:57