Skip to main content
Version: 4.3

Customizing Transactional Emails

This guide will teach you how to customize transactional emails provided by Solidus.

Solidus has built-in transactional emails that notify customers of various events associated with their order. For example, the following actions can trigger an email:

  • Completing order checkout
  • Shipping an order
  • Cancelling an order
  • Completing a batch promtion codes creation (in the admin panel)

Solidus has built-in emails for all of the above scenarios and more. However, given that the default Solidus emails are intended to be very plain, you will likely wish to customize them for your store. You may also want to add new emails, such as a welcome email when a customer creates a new account, an abandoned cart email when a customer leaves their cart without checking out, or a notification that a customer's credit cart has expired if your store uses recurring subscriptions.


Solidus transactional emails use Action Mailer, which is built into Rails. Therefore, most concepts and customizations that apply to emails in Rails also apply to Solidus. Reviewing the Rails Action Mailer Documentation will give you some good ideas about how transactional emails can be customized in Solidus.

Action Mailer emails have two parts:

  1. A view or layout where you can compose and style the email. This could be an html.erb file, or a .txt.erb file for text-only emails.
  2. A mailer, which behaves very similar to a Rails controller. Like a controller, customizing the mailer will let you determine what information is available in your email's view.

Customizing your emails

Suppose we want to inform our customers about the estimated time of arrival (ETA) for their packages. What we'll need to do is:

  1. Customize the right mailer to make it retrieve the ETA for the package.
  2. Customize the email template to render the retrieved ETA along with the other information.

Replacing the default mailer

Let's start by overriding the default mailer to retrieve this information along with all the other things that you currently see in the email. The mailer we want to change is called CartonMailer and is responsible to retrieve the information about the shipped package (called Carton), set subject, to and from of the email, and deliver the actual email.

If we want to override its code we can create a new mailer and instruct Solidus to use our own instead of the default one. Let's create it first:


# frozen_string_literal: true

module AmazingStore
class CartonMailer < Spree::BaseMailer
# Send an email to customers to notify that an individual carton has been
# shipped. If a carton contains items from multiple orders then this will be
# called with that carton one time for each order.
# This custom version also retrieve the estimated time of arrival.
# @option options carton [Spree::Carton] the shipped carton
# @option options order [Spree::Order] one of the orders with items in the carton
# @option options resend [Boolean] indicates whether the email is a 'resend' (e.g.
# triggered by the admin "resend" button)
# @return [Mail::Message]
def shipped_email(options)
@order = options.fetch(:order)
@carton = options.fetch(:carton)
@manifest = @carton.manifest_for_order(@order)

# Here you can add your custom code to calculate the ETA of the package:
@eta = +

options = { resend: false }.merge(options)
@store =
subject = (options[:resend] ? "[#{t('spree.resend').upcase}] " : '')
subject += "#{} #{t('spree.shipment_mailer.shipped_email.subject')} ##{@order.number}"
mail(to:, from: from_address(@store), subject: subject)

Now we need to configure Solidus to use this new mailer instead of the default one:


Spree.config do |config|
config.carton_shipped_email_class = 'AmazingStore::CartonMailer'

This is the list of all the extension points available in Solidus today:

Customize the email template

Next step is adding this new information somewhere in the actual email template, which represents what your customers will see in their inbox. The default templates for the original carton mailers in Solidus are located at core/app/views/spree/carton_mailer/.


Email templates are made of two main parts:

  1. A layout, which is identical in all emails
  2. A content, which is different based on the content.

In this example, we are going to customize the latter, but if you need to make changes to the layout, you can just copy/paste the one provided by Solidus in your own application and change it based on your style preferences.

We can copy paste those files into our project at app/views/awesome_store/carton_mailer/. Now, rendering our ETA is a matter of adding something like this where we prefer within the shipped_email.*.erb templates:

Will be at your door by <%= @eta.to_s(:short) %>

We can preview how our email looks like taking advantage of ActionMailer::Preview: with your local Rails server enabled, visit http://localhost:3000/rails/mailers/ to preview the layouts of mailers provided by Solidus.

And that's it, congratulations! You properly customized your mailer adding a critical information for your users.

Looking for Authentication emails?

Good question, authentication emails (like password reset email or account confirmation email) are not provided in Solidus core, mainly because the authentication plugin (solidus_auth_devise) is optional. If you are using it and you want to customize its emails, you can find this specific mailer in the solidus_auth_devise extension and apply the same procedure followed for customizing the core mailers. The only difference will be with the configuration: because we use Devise for authentication in this case, we can use its own preference to choose which class to use as the authentication mailer:


Devise.setup do |config|
config.mailer = 'AmazingStore::UserMailer'

Adding new transactional emails

If you want to add new transactional emails to your store, you can create your own mailer following the Action Mailer guide as any other Rails application.

If the event that triggers this new email comes from Solidus, we highly recommend to keep your architecture as orthogonal as possible by subscribing to one of the core events.

For example, if we created a new mailer for sending an email to the admin users when a new order is finalized, this could be the code that trigger the email delivery:


# frozen_string_literal: true

module AwesomeStore
class MailerSubscriber
include Omnes::Subscriber

handle :order_finalized,
with: :send_admin_confirmation_email,
id: :admin_order_mailer_send_confirmation_email

# Sends order confirmation email to the admin
# @param event [Omnes::UnstructuredEvent]
def send_admin_confirmation_email(event)
order = event[:order]