Background
- Rails 7.1
- Ruby 3.x
Your production environment is sending emails using Action Mailer and smtp delivery method. You implemented a very common pattern using Action:
class NotificationMailer < ApplicationMailer
def processed_success(user:)
if user.pass_custom_condition?
mail(to: user.email, subject: 'Record processed')
else
Rails.logger.info('Record processed but notification is not delivered')
end
end
end
Inside the Mailer, there is a condition that will return two possible values: a fully configured mail object and automatically is returned a nullable object ActionMailer::Base::MailNull.
The Iteration
An iteration that implements dynamic delivery settings.[1]
class NotificationMailer < ApplicationMailer
include Customizable # concern to apply changes from db
def processed_success(user:)
if user.pass_custom_condition?
mail(to: user.email, subject: 'Record processed')
else
Rails.logger.info('Record processed but notification is not delivered')
end
end
end
Concern
module Customizable
extend ActiveSupport::Concern
included do # include callback into the mailers
after_action :overrides_mail_settings, if: has_overrides_enabled?
end
def overrides_mail_settings
mail.delivery_method.settings.merge!(@business.smtp_settings)
end
def has_overrides_enabled?
@business.customize.present?
end
end
The Bug
Following the suggested implementation in the rails documentation and the backward common pattern, you should receive this error when you try to deliver NotificationMailer#deliver_now
[ArgumentError] SMTP To address may not be blank: []
- Before the callback change, the mailing process finishes with a return value:
Mail::MessageorActionMailer::Base::MailNull. - After adding the callback, the mailer lifecycle expands with a message object copying default values into it.
Solution
Our goal is ensuring backward compatibility by returning: Mail::Message or ActionMailer::Base::MailNull.
Concern
module Customizable
extend ActiveSupport::Concern
included do # include callback into the mailers
after_action :overrides_mail_settings, if: overrides_allowed?
end
def overrides_mail_settings
mail.delivery_method.settings.merge!(@business.smtp_settings)
end
def overrides_allowed?
@business.customize.present? && message.to.present?
end
end
Final Words
Although this fixes our problem and ensures backward compatibility, I would rather move business validation out of the mailer. In my opinion, a mailer class is in the presentation layer responsible for configuring delivery details, customizing layouts and showing content. Any business rule should be in a different component like a service or job.
Bibliography
- Action Mailer Basics โ Ruby on Rails Guides. (2024). Ruby on Rails Guides. https://guides.rubyonrails.org/action_mailer_basics.html
- Rails Api. (2025). rails/actionmailer/lib/action_mailer/base.rb at main ยท rails/rails. GitHub. https://github.com/rails/rails/blob/main/actionmailer/lib/action_mailer/base.rb
โ
Top comments (0)