Recently we had a requirement where emails were to be delivered to a specific group of users by switching the provider at run time. In short, we are required to deliver the emails by dynamically selecting a provider and not just through a pre-configured provider.

Rails provide these Dynamic Delivery Options to override the SMTP settings per mail. But, if you have an existing application with various mailers, applying delivery_method_options will require updating individual mailer action. In this blog, we'll go through the approaches that will work without modifying any existing mailer actions.


Setup

Assuming that your default provider is Sendgrid, we'll create the AlternateSmtpDelivery class to implement the interface for dynamic email provider selection.

# lib/mail/alternate_smtp_delivery.rb

class AlternateSmtpDelivery < ::Mail::SMTP; end
# config/development.rb

YourApp::Application.configure do
  ...
  ActionMailer::Base.add_delivery_method :alternate_smtp_delivery, AlternateSmtpDelivery

  config.action_mailer.alternate_smtp_delivery_settings = {
    :address        => 'smtp.sendgrid.net',
    :port           => '587',
    :authentication => :plain,
    :user_name      => 'apikey',
    :password       => ENV['SENDGRID_API_KEY'],
    :domain         => ENV['SENDGRID_DOMAIN'],
  }
  ...
end

As we are done with the setup, now we will discuss 3 different ways of implementation, You may choose the implementation which suits the project best as per the requirements.

Email providers can be switched at any of the following 3 areas, where each latter definition (in the given order) takes precedence(if defined).

1. Default delivery method per mailer

One of the simpler approaches to switch the email delivery behavior is to use the default delivery_method: <delivery method identifier> macro:

class AlternateMailer < ApplicationMailer
  default delivery_method: :alternate_smtp_delivery
  ...
end

When to use: When you do not have conditionals and the method applies to all actions of the mailer.

2. Switch delivery behavior just before the actual mail delivery

The ActionMailer inherits from AbstractController::Base. It provides access to the after_action callback. This callback can be used to switch to desired delivery behavior.

In the example below, we will use wrap_delivery_behavior! with the required delivery method's identifier (identifier is the symbol which is the first argument passed to ActionMailer::Base.add_delivery_method in the initializer config from the above setup):

class AlternateMailer < ApplicationMailer
  after_action :use_alternate_smtp_delivery, if: :switch_mail_provider?

  private

  def use_alternate_smtp_delivery
    wrap_delivery_behavior! :alternate_smtp_delivery
  end

  def switch_mail_provider?
    # return a truthy/falsy value as per the business logic
  end
end

When to use: When you have conditionals, you can use the after_action to switch delivery behavior per action instead of per mailer like above.

3. Register the mail interceptor

Interceptors allow you to make modifications to emails before they are handed off to the delivery agents. An interceptor class must implement the ::delivering_email(message) method, which will be called before the email is sent. ~ Official Rails Documentation

Example of an interceptor:

# config/initializers/email_provider_interceptor.rb

class EmailProviderInterceptor
  class << self
    def delivering_email(message)
      return unless switch_provider?(message)

      ApplicationMailer.wrap_delivery_behavior(message, :alternate_smtp_delivery)
    end

    private

    def switch_provider?(message)
      # return a truthy/falsy value as per the business logic
    end
  end
end

ActionMailer::Base.register_interceptor(EmailProviderInterceptor)

When to use: When you want to move code away from mailers and want to apply the config in general to all the mailers, you can switch the email delivery behavior in the delivering_email method of the interceptor.


We hope all these 3 approaches will help you to know better practices when switching email delivery behavior at runtime.

Will ❀️ to hear your thoughts/ideas on this or any other Ruby on Rails topic, reach out to me at @ImaKaranV / LinkedIN (Karan Valecha)