Email failover with MultiSMTP

By Harlow Ward

Email is a critical part of our business and we rely on it heavily to deliver notifications to both our customers and Hotel partners.

I reviewed an excellent pull-request recently by Michael Choi that maximizes our Email deliveries by adding support for multiple SMTP providers.

The automatic failover is achieved by overriding the default delivery method with a custom MultiSMTP class.

# config/environments/{staging,production}.rb
HotelTonight::Application.configure do
  config.action_mailer.delivery_method = :multi_smtp
end

The multi-provider functionality is built on top of the Mail gem.

# Gemfile
gem 'mail'

In fact, the inspiration came from a quick source dive into original :smtp delivery method.

vim `bundle show mail`
# check out lib/mail/network/delivery_methods/smtp.rb

If a message fails to send the deliver! method will continue to iterate over the array of smtp_providers until the email has been successfully sent.

# lib/multi_smtp.rb
class MultiSMTP < Mail::SMTP
  cattr_accessor :smtp_providers

  def initialize(default_settings)
    @default_settings = default_settings
  end

  def deliver!(mail)
    smtp_providers.each_with_index do |smtp_provider, index|
      self.settings = default_settings.merge(smtp_provider)

      begin
        super(mail)
        break
      rescue Exception => e
        if last_smtp_provider? index
          # Send Airbrake notification
        else
          # Log error message
        end
      end
    end
  end

  private

  def smtp_providers
    self.class.smtp_providers
  end

  def last_smtp_provider?(index)
    (smtp_providers.size - 1) == index
  end

  attr_reader :default_settings
end

ActionMailer::Base.add_delivery_method(:multi_smtp, MultiSMTP)

In an initializer we configure the MultiSMTP class with an array of (1..N) SMTP Providers.

# config/initializers/multi_smtp.rb
sendgrid_settings = {
  address: 'smtp.sendgrid.net',
  authentication: :plain,
  domain: 'hoteltonight.com',
  password: ENV['SENDGRID_PASSWORD'],
  port: '587',
  user_name: ENV['SENDGRID_USERNAME'],
}

other_smtp_settings = {
  # Other SMTP settings
}

MultiSMTP.smtp_providers = [sendgrid_settings, other_smtp_settings]

When delivering emails to your customers are of the utmost importance, don’t let a single provider become a single point of failure - protect yourself against service interruptions with MultiSMTP.

Written by Harlow Ward

Read more posts by Harlow, and follow Harlow on Twitter.

Interested in building something great?

Join us in building the worlds most loved hotel app.
View our open engineering positions.