A mailer renders its bodies with Hanami View. Each mailer builds its own view behind the scenes, so your templates, exposures and helpers behave just like they do elsewhere in your app.
Templates
A mailer has two body formats, :html and :text, each rendered from its own template. The format is the first extension in the template file name:
-
app/templates/mailers/welcome.html.erbprovides the:htmlbody -
app/templates/mailers/welcome.text.erbprovides the:textbody
The template name is inferred from the mailer’s name. Bookshelf::Mailers::Welcome renders the mailers/welcome template, found under app/templates/mailers/. A mailer in a deeper namespace keeps its path: Bookshelf::Mailers::Notifications::Welcome renders mailers/notifications/welcome.
Your exposures are available in the templates as locals:
<%# app/templates/mailers/welcome.html.erb %>
Welcome, !
<%# app/templates/mailers/welcome.text.erb %>
Welcome, !
Formats
Both HTML and text will render by default, producing a multipart email. If only one template exists, the mailer renders that format only.
To render a single format explicitly, pass format: to deliver (or prepare):
welcome_mailer.deliver(user:, format: :html)
When rendering both formats, a missing template for one of them is tolerated, and only the available format is rendered. Requesting a specific format whose template is missing raises a Hanami::View::TemplateNotFoundError.
View integration in your app
Inside a Hanami app, mailer views are built from your slice’s view class. This means mailer templates share everything your other view templates have: the slice’s context, parts, scopes, template paths and helpers, including i18n.
The only thing not available to mailer templates is request-related state — request, session, flash and csrf_token — because mailers aren’t rendered from a request. Using one of these in a mailer template raises an error.
Customizing the mailer view
To customize rendering across all your mailers, define a Mailers::View in your slice at app/mailers/view.rb. Because it lives in your slice, it’s configured automatically, and every mailer’s view inherits from it.
This is a good home for anything every email needs. For example, since mailer templates have no request to build links from, you might expose your app’s base URL for use across all your emails:
# app/mailers/view.rb
include Deps["settings"]
expose :base_url do
settings.base_url
end
end
end
end
<%# app/templates/mailers/welcome.html.erb %>
Welcome, ! Browse the shelves.
Decorating exposures with parts
When Hanami View is integrated, decorate exposes a value wrapped in a matching view part, so you can add presentation methods to the data your templates render:
from "welcome@bookshelf.test"
to { user.email }
subject "Welcome!"
decorate :user
end
decorate is shorthand for expose(..., decorate: true).
Layouts
Mailers don’t use a layout by default. To wrap your bodies in one, configure config.layout on the mailer (or your base mailer class), pointing at a template under your view’s layouts directory:
config.layout = "mailer"
end
end
Rendering without Hanami View
Mailers can render their bodies without Hanami View. This is useful for standalone projects that don’t use it, or as a hook for another rendering system.
Override render_view, returning the body for each format:
from "custom@example.com"
to { user[:email] }
subject "Hello"
expose :user
private
user = input[:user]
case format
when :html then "<h1>Hello, !</h1>"
when :text then "Hello, !"
end
end
end
If Hanami View is installed but you don’t want a mailer building a view from it, turn off automatic view building with config.integrate_view = false. Your render_view then takes full responsibility for rendering.
Standalone configuration
Outside a Hanami app, a mailer needs to know where to find its templates. Set config.paths, and optionally config.template to name the template explicitly:
config.paths = ["app/templates/mailers"]
config.template = "welcome"
from "welcome@example.com"
to { user[:email] }
subject "Welcome!"
expose :user
end
To render through an existing, already-configured view class, set config.view_class. The mailer’s view inherits that class’s configuration — context, parts, scopes, paths and helpers — which is exactly how mailers in a Hanami app pick up the app’s view behavior. With a view class configured, you typically don’t need to set config.paths yourself.