Skip to main content
Every email Spree sends — order confirmations, shipping notices, password resets, staff invitations — renders from a Liquid template written in MJML. Liquid fills in the data; MJML turns a dozen layout tags into the nested tables, inline styles and Outlook workarounds that email clients need, and makes the email stack on phones. To change an email, you copy its template into your app and edit it. Nothing else changes: Spree still picks the recipient, the language and the sender, and delivers the email through your SMTP provider.
Templates read plain data — the same fields the Store API returns, plus what only an email needs — never Ruby objects. The same template can later run outside Ruby, and merchants will be able to edit templates safely from the dashboard.

Where templates live

A template sits at its email’s path with a .liquid extension. To override one, create the file at the same path in your app: The path is also the template’s name, for example spree/order_mailer/confirm_email. Email Template Variables lists every customer email’s template and the data it receives. Start from Spree’s own template rather than a blank file: customer emails are in spree/emails/app/views and staff emails and the layout in spree/core/app/views on GitHub. Your file wins as soon as it exists, and edits to it show on the next email without a restart. There is nothing to register. The one exception: Rails only reads server/app/views if the folder existed when the app started, so restart once after creating that folder. API-only apps often start without it.

Anatomy of a template

server/app/views/spree/order_mailer/confirm_email.liquid
  • The subject is the subject line at the top, and it is Liquid too.
  • The body is a list of MJML sections. The layout wraps it with the store’s logo and footer.
  • Copy either comes from Spree’s translations through the t filter, which keeps one template working in every language, or is written straight into the template for a single-language store.
The layout defines named styles you can use through mj-class: heading, greeting, lead, note, section-heading and body. For the MJML tags themselves, see the MJML documentation.

Partials

Pull a shared piece in with {% render %}. The name maps to a file with a leading underscore, as Rails partials do: 'spree/shared/line_item' is app/views/spree/shared/_line_item.liquid. A partial only sees the values you pass it. Override a partial the same way as a template, by creating the file at its path in your app. Every email that uses it changes.

Filters

Besides Liquid’s standard filters, templates have these. The names follow common Liquid conventions where they mean the same thing. Most amounts already arrive formatted, as display_total, display_amount and the like, so money is only needed for a raw amount.

Escaping

Everything a template prints is HTML-escaped. A customer who types <a href="https://evil.test">Click to verify</a> as their name sees that text in the email, not a live link — and so does the store owner reading the new-order notification. raw turns escaping off for one value. Only use it for HTML that was cleaned when it was saved, such as a product’s rich-text description. Never use it on a name, an address or a note.

The plain-text version

Spree builds each email’s plain-text version from its HTML, writing links as label (url), so the two can never drift apart. To write the text by hand instead, add a .text.liquid file next to the template:
server/app/views/spree/order_mailer/confirm_email.text.liquid
Nothing is escaped in the text version.

Mistakes surface in development

In development and test, a variable that does not exist raises an error instead of printing nothing, so {{ order.nubmer }} fails your spec rather than sending a blank. In production it prints nothing. A template that loops endlessly fails that one email instead of stalling your background jobs. To look at every email with real data, open the mailer previews at /rails/mailers on your Spree server.

Your own mailers

A mailer that inherits Spree::BaseMailer and renders its own ERB views with mail keeps working. Spree wraps its HTML in the same layout as every other email, so it carries the store’s logo, header and footer. The spree/shared/mailer_hero and spree/shared/mailer_button partials are still there for those views. To give a new email the same data contract as Spree’s own, render it from a Liquid template instead:
server/app/mailers/spree/welcome_mailer.rb
The template goes at server/app/views/spree/welcome_mailer/welcome_email.liquid. email_data serializes a record the way templates read it; pass a URL that carries a token as its own variable rather than serializing it.