Skip to content

Instantly share code, notes, and snippets.

@jvenezia
Last active January 4, 2023 13:44
Show Gist options
  • Save jvenezia/6776931594aa47936a87 to your computer and use it in GitHub Desktop.
Save jvenezia/6776931594aa47936a87 to your computer and use it in GitHub Desktop.
Rails mailer structure

Rails mailer structure

Your application is growing, and you are starting to have a complex mailing system: notification emails, retention emails, misc user emails, admin emails, etc...

It's time to clean up your mailers !

Existing mailer

You may already have a single mailer, responsible of every emails, like this one:

app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  SENDER_EMAIL = '[email protected]'
  SENDER = "elCurator <#{SENDER_EMAIL}>"
  default from: SENDER
  
  ...
  
  def new_article_notification(user, article)
    @user = user
    @article = article
    
    I18n.with_locale(UserMailer.user_locale user) do
      mail( to: user.email,
            subject: I18n.t('user_mailer.new_article_notification.subject'))
    end
  end
  
  ...
  
  def one_month_inactive_user(user)
    @user = user
    
    I18n.with_locale(UserMailer.user_locale user) do
      mail to: user.email,
           subject: I18n.t('user_mailer.one_month_inactive_user.subject')
    end
  end
  
  ...
  
  private

  def self.user_locale(user)
    user.try(:locale) || ENV['DEFAULT_LOCALE'] || :fr
  end
end

Associated templates should be:

  • app/views/user_mailer/new_article_notification.html.haml
  • app/views/user_mailer/one_month_inactive_user.html.haml

And I18n localizaion files:

  • config/locales/mailers/user_mailer.en.yml
  • config/locales/mailers/user_mailer.fr.yml

Split the mailer

Let's split our single mailer to separate emails with different responsibilities. Here we'll have a NotificationMailer and a RetentionMailer.

app/mailers/notification_mailer.rb

class NotificationMailer < ActionMailer::Base
  SENDER_EMAIL = '[email protected]'
  SENDER = "elCurator <#{SENDER_EMAIL}>"
  default from: SENDER
  
  ...
  
  def new_article_notification(user, article)
    @user = user
    @article = article
    
    I18n.with_locale(NotificationMailer.user_locale user) do
      mail( to: user.email,
            subject: I18n.t('notification_mailer.new_article_notification.subject'))
    end
  end
  
  ...
    
  private

  def self.user_locale(user)
    user.try(:locale) || ENV['DEFAULT_LOCALE'] || :fr
  end
end

app/mailers/retention_mailer.rb

class RetentionMailer < ActionMailer::Base
  SENDER_EMAIL = '[email protected]'
  SENDER = "elCurator <#{SENDER_EMAIL}>"
  default from: SENDER
  
  ...
  
  def one_month_inactive_user(user)
    @user = user
    
    I18n.with_locale(RetentionMailer.user_locale user) do
      mail to: user.email,
           subject: I18n.t('retention_mailer.one_month_inactive_user.subject')
    end
  end
  
  ...
    
  private

  def self.user_locale(user)
    user.try(:locale) || ENV['DEFAULT_LOCALE'] || :fr
  end
end

Templates have to be moved this way:

  • app/views/notification_mailer/new_article_notification.html.haml
  • app/views/retention_mailer/one_month_inactive_user.html.haml

Note that the I18n paths have change and are now separated for each mailer. Split your localization files too:

  • config/locales/mailers/notification_mailer.en.yml
  • config/locales/mailers/notification_mailer.fr.yml
  • config/locales/mailers/retention_mailer.en.yml
  • config/locales/mailers/retention_mailer.fr.yml

Code duplicaton

We have duplicated code here. Our two mailers uses the same sender. One way to clean this is to created an ApplicationMailer who will be inherited by our other mailers. Like so, NotificationMailer < ActionMailer::Base becomes NotificationMailer < ApplicationMailer.

app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  SENDER_EMAIL = '[email protected]'
  SENDER = "elCurator <#{SENDER_EMAIL}>"
  default from: SENDER
    
  private

  def self.user_locale(user)
    user.try(:locale) || ENV['DEFAULT_LOCALE'] || :fr
  end
end

app/mailers/notification_mailer.rb

class NotificationMailer < ApplicationMailer
  ...
  
  def new_article_notification(user, article)
    @user = user
    @article = article
    
    I18n.with_locale(NotificationMailer.user_locale user) do
      mail( to: user.email,
            subject: I18n.t('notification_mailer.new_article_notification.subject'))
    end
  end
  
  ...
end

app/mailers/retention_mailer.rb

class RetentionMailer < ApplicationMailer
  ...
  
  def one_month_inactive_user(user)
    @user = user
    
    I18n.with_locale(RetentionMailer.user_locale user) do
      mail to: user.email,
           subject: I18n.t('retention_mailer.one_month_inactive_user.subject')
    end
  end
  
  ...
end

Move your mailing templates in a different folder

You may want to move you templates in a mailers folder now, to have something like this:

  • app/views/mailers/notification_mailer/new_article_notification.html.haml
  • app/views/mailers/retention_mailer/one_month_inactive_user.html.haml

Rails defaults settings wont work if you try this, because it won't search your templates within the app/views/mailers folder. One way to do this is to change the default template path in the ApplicationMailer, to find templates in app/views/mailers/{mailer_name}.

app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  SENDER_EMAIL = '[email protected]'
  SENDER = "elCurator <#{SENDER_EMAIL}>"
  default from: SENDER
  default template_path: -> (mailer) { "mailers/#{mailer.class.name.underscore}" }
    
  private

  def self.user_locale(user)
    user.try(:locale) || ENV['DEFAULT_LOCALE'] || :fr
  end
end

Now that we moved our templates in another folder, make shure that your relative I18n paths are not broken. All you mailer locales have to be moved in the mailers yml property.

app/views/mailers/notification_mailer/new_article_notification.html.haml

-# This relavite I18n path will search 'mailers.notification_mailer.new_article_notification.message'
.message= i18n.t('.message')

config/locales/mailers/notification_mailer.en.yml

en:
  mailers:
    notification_mailer:
      new_article_notification:
        message: New article!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment