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)