Previewing mailers in Hanami 3.0

Our Hanami 3.0 release is coming soon (a release candidate in a matter of weeks), and one of the big new features is a rebuilt hanami-mailer gem.

Here I’ll outline how mailers will integrate into Hanami apps, so that you can understand how it all fits together, give it a try yourself, and share your feedback.


Hanami Mailer is completely rebuilt!

For starters, go check out the Hanami Mailer README. This is a completely rebuilt gem designed in the same spirit as every other first-party Hanami component, like Hanami Action and Hanami View.

The README outlines all the key capabilities and APIs. This is 99% of what you’ll be writing for your mailers in Hanami apps, so please have a read and make sure your own expected use cases are covered.

App and slices have base mailer classes

Following our standard approach, we’ll generate app/mailer.rb and slices/[your_slice]/mailer.rb base classes. These will be good places to put your standard headers (which can be overridden in subclasses as required).

require "hanami-mailer"

module MyApp
  class Mailer < Hanami::Mailer
    # A standard `from` header for every mailer
    from "noreply@example.com"
  end
end

Mailers will be generated into mailers/, but can go anywhere

For all our standard components, we need to pick a default location. For mailers, this will be mailers/ (and the Mailers namespace) in the app or slice.

# app/mailers/user_welcome.rb

module MyApp
  module Mailers
    class UserWelcome < MyApp::Mailer
      to { |user:| user.email }
      expose :user      
    end
  end
end

We will not be encouraging a “Mailer” suffix on these class names. Their presence within their namespace is indication enough that it is a mailer.

We will provide a hanami generate mailer CLI command to generate these files.

Like every Hanami component, however, mailers can be relocated to wherever you want them. So if you want to organise your app differently, you’re very welcome to!

Mailer templates are loaded from templates/mailers/

Mailer templates are rendered via Hanami View (provided you keep it in your app’s Gemfile), and are configured to load their templates from templates/mailers/

In the case of the UserWelcome mailer above, it will render these two templates:

  • app/templates/mailers/user_welcome.html.erb
  • app/templates/mailers/user_welcome.text.erb

You don’t need to provide both formats. You can provide just one of :html or :text if that’s your preference for your emails.

All standard view facilities are available for your mailers

Mailers render their views via an internally-created view class. For mailers in Hanami apps, this class will inherit from your app or slice’s base view class (e.g. MyApp::View). This means that all framework-provided view facilities are available for your mailers too, such as your standard view context and helpers.

Any view functionality that depends on a current request will of course not work, since mailers are not tied to a web request/response cycle.

You’ll primarily use mailers as injectable dependencies

Mailers are designed to work as injectable dependencies, and that’s exactly how you’re likely to use them in your apps.

For example, to send the user welcome email:

module MyApp
  module Account
    class SignUp < MyApp::Operation
      include Deps["mailers.user_welcome"]

      def call(attrs)
        user = step create_user(attrs)

        user_welcome.deliver(user:)

        user
      end

      private

      def create_user(attrs)
        user = create_the_user # (your logic here)
        Success(user)
      end
    end
  end
end

An SMTP delivery method is created based on convention env vars

Your mailers will deliver via a Hanami::Mailer::Delivery::SMTP delivery method instance, if you provide the following env vars:

  • SMTP_ADDRESS
  • SMTP_PORT
  • SMTP_USERNAME
  • SMTP_PASSWORD
  • SMTP_AUTHENTICATION

Example values:

SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=postmaster@example.com
SMTP_PASSWORD=s3cr3t
SMTP_AUTHENTICATION=plain

Provide as many or as few as you need to connect to your SMTP server.

These env vars can be prefixed by slice name to set up a delivery method for a specific slice:

MY_SLICE__SMTP_ADDRESS=smtp.example.com
MY_SLICE__SMTP_PORT=587

This delivery method is registered in your containers as "mailers.delivery_method", so you can access it independently if required.

A test delivery method is set up as a fallback

When no SMTP ENV vars are available, a Hanami::Mailer::Delivery::Test delivery method will be used for your mailers.

This delivery method does not actually send your emails. Instead, it collects the mail objects in memory:

MyApp["mailers.delivery_method"].deliveries # => [your, delivered, emails]

If this fallback runs when Hanami is in the :production env, then it will emit a warning to make sure you haven’t misconfigured your app:

No SMTP configuration found for my_app in production. Falling back to the test delivery method. Mail will NOT be sent.

Set SMTP_ADDRESS (and related SMTP_* variables), or register a custom :mailers provider. See https://hanakai.org/learn/hanami/mailers for more.

We warn rather than fail to boot because it’s reasonable for a user to deploy their app to production before they’re ready to send emails. We don’t want to get in the way of that.

A test delivery method is standard in :test env

When running your app with HANAMI_ENV=test, then a test delivery method is used by default, even if SMTP_* env vars exist. The test delivery method is more appropriate for testing, and we don’t want you to accidentally make real deliveries of test-generated emails.

You can customise mailer setup with your own :mailers provider

If you want to set up a custom delivery method or customise any other aspect of the delivery method setup, you can create your own :mailers provider.

Hanami.app.register_provider :mailers, namespace: true do
  start do
    delivery_method =
      if Hanami.app.env == :test
        Hanami::Mailer::Delivery::Test.new
      else
        MyCustomDeliveryMethod.new
      end

    register "delivery_method", delivery_method
  end
end

When you use your own provider, you’ll need to take care of the test-mode delivery method yourself, as shown above.


OK! I think that’s all the ins and outs you need to know. I’m keen for your feedback. Please let me know if there’s anything else you think we should be doing for mailers in Hanami! :cherry_blossom:

3 Likes