Hanami 1.3 > Hanami 2.2 : tips & notes about my journey

Use case #11: Sending emails

I got to the point where I needed to send transactional emails with my Hanami app, I wanted to render them with templates and that they would be easy to send, and ideally integrated into the framework sot that I could come from an action, a job, a rake task…

After a few tries I found that the hanami-mailer was a good choice to start, despite its rough edges that I hope I will be have the level to polish some day. Thanks to this episode from Hanami Mastery I had the instructions on how to integrate it so I adapted it into my app.

The instructions below are heavily inspired from what I learnt from the above article, all credit is due to @swilgosz , thanks a lot to him! :cherry_blossom:

First you’ll need to add the gem to your gemfile and call bundle install

# Gemfile
gem "hanami-mailer", github: "hanami/mailer", branch: "main"

Then you need to load what you need in a provider. I decided to use one provider per slice to load my templates aka mailers. I felt that it suits well with the fact that each slice will have its own way to create and send emails.

# slices/my_slice/config/providers/mailer.rb

MySlice::Slice.register_provider(:mailer, namespace: true) do
  prepare do
    require "hanami/mailer"
    
    configuration = Hanami::Mailer::Configuration.new do |config|
      config.root = target.root.join("mailers")
      config.default_charset = "UTF-8"
          
      if ENV['HANAMI_ENV'] != "production"
        config.delivery_method = :logger, {logger: Logger.new(STDOUT)}
        # FYI config.delivery_method = :test  is another possible option
      else
        # here you can specify more complex options for your SMTP params
        config.delivery_method = :smtp, {:enable_starttls_auto => false}
      end
    end

    register "configuration", configuration
  end

  start do
    configuration = target['mailer.configuration']
    # Hanami::Mailer requires to initialize all mailers before finalizing
    # config, and finalizing config before using them.
    mailers = Dir[configuration.root.join('*.rb')]
    mailers.each do |path|
      mailer_name = File.basename(path, '.*')
      target["mailers.#{mailer_name}"]
    end

    Hanami::Mailer.finalize(configuration)

    register "mailer", true
  end
end

=> With that config, all “mailers” found in slices/my_slice/mailers directory will be loaded and ready to be used from dependency container.
A mailer is a ruby class inheriting from Hanami::Mailer class

Here is a sample yet realistic mailer:

# slices/my_slice/mailers/notify_print_is_ready.rb

require "hanami/mailer"

module MySlice
  module Mailers
    class NotifyPrintIsReady < Hanami::Mailer
      
      include Deps['mailer.configuration']

      from "Support team <support@acme.com>"
      to ->(locals) { locals.fetch(:to_email) }
      
      subject ->(locals) { "Your print '#{locals.fetch(:project_name)}' is ready ✅"  } 
      template 'notify_print_is_ready'

    end
  end
end

This mailer will need an associated template, that will be rendered before sending the email.

# slices/my_slice/mailers/notify_print_is_ready.html.erb

<html>
  <body>
    <p>Your print <%= locals.fetch(:project_name) %> is ready</p>
  <body>
</html>

The email can then be sent from anywhere in your code:

MySlice::Slice["mailers.notify_print_is_ready"].deliver({
  to_email:       "jim.halpert@gmail.com",      
  project_name:   chapter_print.name,
})

And voilà :tada:

Unfortunately it does not seem possible to inherit from a base layout, I’d love to help on that some day, let me know if you have any pointer or any ongoing work about the topic.

Thanks for reading, see you next time