# [**Email Templates**](https://github.com/forwardemail/email-templates) [![build status](https://github.com/forwardemail/email-templates/actions/workflows/ci.yml/badge.svg)](https://github.com/forwardemail/email-templates/actions/workflows/ci.yml) [![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://lass.js.org) [![license](https://img.shields.io/github/license/forwardemail/email-templates.svg)](LICENSE) Create, [preview][preview-email] (browser/iOS Simulator), and send custom email templates for [Node.js][node]. Made for [Forward Email][forward-email] and [Lad][]. > **Need to send emails that land in the inbox instead of spam folder? [Click here to learn how to send JavaScript contact forms and more with Node.js](https://forwardemail.net/docs/how-to-javascript-contact-forms-node-js)** ## Table of Contents * [Install](#install) * [Preview](#preview) * [Usage](#usage) * [Debugging](#debugging) * [Basic](#basic) * [Attachments](#attachments) * [Automatic Inline CSS via Stylesheets](#automatic-inline-css-via-stylesheets) * [Render HTML and/or Text](#render-html-andor-text) * [Localization](#localization) * [Text-Only Email (no HTML)](#text-only-email-no-html) * [Prefix Subject Lines](#prefix-subject-lines) * [Custom Text Template](#custom-text-template) * [Custom Template Engine (e.g. EJS)](#custom-template-engine-eg-ejs) * [Custom Default Message Options](#custom-default-message-options) * [Custom Rendering (e.g. from a MongoDB database)](#custom-rendering-eg-from-a-mongodb-database) * [Absolute Path to Templates](#absolute-path-to-templates) * [Open Email Previews in Firefox](#open-email-previews-in-firefox) * [Options](#options) * [Tips](#tips) * [Purge unused CSS](#purge-unused-css) * [Optimized Pug Stylesheet Loading](#optimized-pug-stylesheet-loading) * [Plugins](#plugins) * [Breaking Changes](#breaking-changes) * [v12.0.0](#v1200) * [v11.0.0](#v1100) * [v10.0.0](#v1000) * [v9.0.0](#v900) * [v8.0.0](#v800) * [v7.0.0](#v700) * [v6.0.0](#v600) * [v5.0.0](#v500) * [v4.0.0](#v400) * [v3.0.0](#v300) * [Related](#related) * [Contributors](#contributors) * [License](#license) ## Install > By default we recommend [pug][] for your template engine, but you can use [any template engine][supported-engines]. Note that [preview-email][] is an optional dependency and is extremely helpful for rendering development previews of your emails automatically in your browser. [npm][]: ```sh npm install email-templates preview-email pug ``` ## Preview We've added [preview-email][] by default to this package. This package allows you to preview emails in the browser and in the iOS Simulator. This means that (by default) in the development environment (e.g. `NODE_ENV=development`) your emails will be rendered to the tmp directory for you and automatically opened in the browser. If you have trouble previewing emails in your browser, you can configure a `preview` option which gets passed along to [open's options][open-options] (e.g. `preview: { open: { app: 'firefox' } }`). See the example below for [Open Email Previews in Firefox](#open-email-previews-in-firefox). ## Usage ### Debugging #### Environment Flag If you run into any issues with configuration, files, templates, locals, etc, then you can use the `NODE_DEBUG` environment flag: ```sh NODE_DEBUG=email-templates node app.js ``` This will output to the console all debug statements in our codebase for this package. #### Inspect Message As of v3.6.1 you can now inspect the message passed to `nodemailer.sendMail` internally. In the response object from `email.send`, you have access to `res.originalMessage`: ```js email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(res => { console.log('res.originalMessage', res.originalMessage) }) .catch(console.error); ``` ### Basic > You can swap the `transport` option with a [Nodemailer transport][nodemailer-transport] configuration object or transport instance. We highly recommend using [Forward Email][forward-email] for your transport (it's the default in [Lad][]). > > If you want to send emails in `development` or `test` environments, set `options.send` to `true`. ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, // uncomment below to send emails in development/test env: // send: true transport: { jsonTransport: true } }); email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` The example above assumes you have the following directory structure: ```sh . ├── app.js └── emails └── mars ├── html.pug └── subject.pug ``` And the contents of the `pug` files are: > `html.pug`: ```pug p Hi #{name}, p Welcome to Mars, the red planet. ``` > `subject.pug`: ```pug = `Hi ${name}, welcome to Mars` ``` ### Attachments Please reference [Nodemailer's attachment documentation][attachments] for further reference. > If you want to set default attachments sent with every email: ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com', attachments: [ { filename: 'text1.txt', content: 'hello world!' } ] } }); email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` > If you want to set attachments sent individually: ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true } }); email .send({ template: 'mars', message: { to: 'elon@spacex.com', attachments: [ { filename: 'text1.txt', content: 'hello world!' } ] }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` ### Automatic Inline CSS via Stylesheets Simply include the path or URL to the stylesheet in your template's ``: ```pug link(rel="stylesheet", href="/css/app.css", data-inline) ``` This will look for the file `/css/app.css` in the `build/` folder. Also see [Optimized Pug Stylesheet Loading](#optimized-pug-stylesheet-loading) below. If this asset is in another folder, then you will need to modify the default options when creating an `Email` instance: ```js const email = new Email({ // juice: true, // Override juice global settings juiceSettings: { tableElements: ['TABLE'] }, juiceResources: { // set this to `true` (since as of v11 it is `false` by default) applyStyleTags: true, // <------------ you need to set this to `true` webResources: { // // this is the relative directory to your CSS/image assets // and its default path is `build/`: // // e.g. if you have the following in the ` of your template: // `` // then this assumes that the file `build/style.css` exists // relativeTo: path.resolve('build') // // but you might want to change it to something like: // relativeTo: path.join(__dirname, '..', 'assets') // (so that you can re-use CSS/images that are used in your web-app) // } } }); ``` ### Render HTML and/or Text If you don't need this module to send your email, you can still use it to render HTML and/or text templates. Simply use the `email.render(view, locals)` method we expose (it's the same method that `email.send` uses internally). > If you need to render a specific email template file (e.g. the HTML version): ```js const Email = require('email-templates'); const email = new Email(); email .render('mars/html', { name: 'Elon' }) .then(console.log) .catch(console.error); ``` The example above assumes you have the following directory structure (note that this example would only render the `html.pug` file): ```sh . ├── app.js └── emails └── mars ├── html.pug ├── text.pug └── subject.pug ``` The Promise for `email.render` resolves with a String (the HTML or text rendered). > If you need pass juiceResources in render function, with this option you don't need create Email instance every time ```js const Email = require('email-templates'); const email = new Email(); email .render({ path: 'mars/html', juiceResources: { webResources: { // view folder path, it will get css from `mars/style.css` relativeTo: path.resolve('mars') } } }, { name: 'Elon' }) .then(console.log) .catch(console.error); ``` The example above will be useful when you have a structure like this, this will be useful when you have a separate CSS file for every template ```sh . ├── app.js └── emails └── mars ├── html.pug ├── text.pug ├── subject.pug └── style.css ``` The Promise for `email.render` resolves with a String (the HTML or text rendered). > If you need to render all available template files for a given email template (e.g. `html.pug`, `text.pug`, and `subject.pug` – you can use `email.renderAll` (this is the method that `email.send` uses). ```js const Email = require('email-templates'); const email = new Email(); email .renderAll('mars', { name: 'Elon' }) .then(console.log) .catch(console.error); ``` > If you need to render multiple, specific templates at once (but not all email templates available), then you can use `Promise.all` in combination with `email.render`: ```js const Email = require('email-templates'); const email = new Email(); const locals = { name: 'Elon' }; Promise .all([ email.render('mars/html', locals), email.render('mars/text', locals) ]) .then(([ html, text ]) => { console.log('html', html); console.log('text', text); }) .catch(console.error); ``` ### Localization All you need to do is simply pass an [i18n][] configuration object as `config.i18n` (or an empty one as this example shows to use defaults). > Don't want to handle localization and translation yourself? Just use [Lad][lad] – it's built in and uses [mandarin][] (with automatic Google Translate support) under the hood. ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true }, i18n: {} // <------ HERE }); email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { locale: 'en', // <------ CUSTOMIZE LOCALE HERE (defaults to `i18n.defaultLocale` - `en`) // is your user french? // locale: 'fr', name: 'Elon' } }) .then(console.log) .catch(console.error); ``` Then slightly modify your templates to use localization functions. > `html.pug`: ```pug p= `${t('Hi')} ${name},` p= t('Welcome to Mars, the red planet.') ``` > `subject.pug`: ```pug p= `${t('Hi')} ${name}, ${t('welcome to Mars')}` ``` Note that if you use [Lad][], you have a built-in filter called `translate`: ```pug p: :translate(locale) Welcome to Mars, the red planet. ``` #### Localization using Handlebars template engine If you are using handlebars and you are using localization files with named values, you will quickly see that there is no way to properly call the `t` function in your template and specify named values. If, for example you have this in your translation file: ```json { "greetings": "Hi {{ firstname }}", "welcome_message": "Welcome to Mars, the red planet." } ``` And you would like to use it in your template like this: > `html.hbs`: ```handlebars

{{ t "greetings" firstname="Marcus" }}

{{ t "welcome_message" }}

``` This would not work because the second argument sent by handlebars to the function would be a handlebar helper options object instead of just the named values. A possible workaround you can use is to introduce your own translation helper in your template locals: ```js email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { locale: 'en', // <------ CUSTOMIZE LOCALE HERE (defaults to `i18n.defaultLocale` - `en`) // is your user french? // locale: 'fr', name: 'Elon', $t(key, options) { // <------ THIS IS OUR OWN TRANSLATION HELPER return options.data.root.t( { phrase: key, locale: options.data.root.locale }, options.hash ); } } }) .then(console.log) .catch(console.error); ``` Then slightly modify your templates to use your own translation helper functions. > `html.hbs`: ```handlebars

{{ $t "greetings" firstname="Marcus" }}

{{ $t "welcome_message" }}

``` ### Text-Only Email (no HTML) If you wish to have only a text-based version of your email you can simply pass the option `textOnly: true`. Regardless if you use the `htmlToText` option or not (see next example), it will still render only a text-based version. ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true }, textOnly: true // <----- HERE }); email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` ### Prefix Subject Lines You can pass an option to prefix subject lines with a string, which is super useful for deciphering development / staging / production environment emails. For example, you could make it so on non-production environments the email is prefixed with a `[DEVELOPMENT] Some Subject Line Here`. You could do this manually by passing a `message.subject` property, however if you are storing your subject lines in templates (e.g. `subject.ejs` or `subject.pug`) then it's not as easy. Simply use the `subjectPrefix` option and set it to whatever you wish (**note you will need to append a trailing space if you wish to have a space after the prefix; see example below**): ```js const Email = require('email-templates'); const env = process.env.NODE_ENV || 'development'; const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true }, subjectPrefix: env === 'production' ? false : `[${env.toUpperCase()}] `; // <--- HERE }); ``` ### Custom Text Template > By default we use `html-to-text` to generate a plaintext version and attach it as `message.text`. If you'd like to customize the text body, you can pass `message.text` or create a `text` template file just like you normally would for `html` and `subject`. You may also set `config.htmlToText: false` to force the usage of the `text` template file. ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true }, htmlToText: false // <----- HERE }); email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` > `text.pug`: ```pug | Hi #{name}, | Welcome to Mars, the red planet. ``` ### Custom Template Engine (e.g. EJS) 1. Install your desired template engine (e.g. [EJS][]) [npm][]: ```sh npm install ejs ``` 2. Set the extension in options and send an email ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com' }, transport: { jsonTransport: true }, views: { options: { extension: 'ejs' // <---- HERE } } }); ``` ### Custom Default Message Options You can configure your Email instance to have default message options, such as a default "From", an unsubscribe header, etc. For a list of all available message options and fields see [the Nodemailer message reference](https://nodemailer.com/message/). > Here's an example showing how to set a default custom header and a list unsubscribe header: ```js const Email = require('email-templates'); const email = new Email({ message: { from: 'test@example.com', headers: { 'X-Some-Custom-Thing': 'Some-Value' }, list: { unsubscribe: 'https://example.com/unsubscribe' } }, transport: { jsonTransport: true } }); ``` ### Custom Rendering (e.g. from a MongoDB database) You can pass a custom `config.render` function which accepts two arguments `view` and `locals` and must return a `Promise`. Note that if you specify a custom `config.render`, you should have it use `email.juiceResources` before returning the final HTML. The example below shows how to do this. If you wanted to read a stored EJS template from MongoDB, you could do something like: ```js const ejs = require('ejs'); const email = new Email({ // ... render: (view, locals) => { return new Promise((resolve, reject) => { // this example assumes that `template` returned // is an ejs-based template string // view = `${template}/html` or `${template}/subject` or `${template}/text` db.templates.findOne({ name: view }, (err, template) => { if (err) return reject(err); if (!template) return reject(new Error('Template not found')); let html = ejs.render(template, locals); html = await email.juiceResources(html); resolve(html); }); }); } }); ``` ### Absolute Path to Templates As of v5.0.1+ we now support passing absolute paths to templates for rendering (per discussion in [#320](https://github.com/forwardemail/email-templates/issues/320). For both `email.send` and `email.render`, the `template` option passed can be a relative path or absolute: > Relative example: ```js email .send({ template: 'mars', message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` > Absolute example: ```js const path = require('path'); // ... email .send({ template: path.join(__dirname, 'some', 'folder', 'mars') message: { to: 'elon@spacex.com' }, locals: { name: 'Elon' } }) .then(console.log) .catch(console.error); ``` ### Open Email Previews in Firefox The `preview` option can be a custom Object of options to pass along to [open's options][open-options]. > Firefox example: ```js const email = new Email({ // ... preview: { open: { app: 'firefox', wait: false } } }); ``` ## Options For a list of all available options and defaults [view the configuration object](src/index.js), or reference the list below: * `views` (Object) * `root` (String) - defaults to the current working directory's "emails" folder via `path.resolve('emails')` * `options` (Object) * `extension` (String) - defaults to `'pug'`, and is the default file extension for templates * `map` (Object) - a template file extension mapping, defaults to `{ hbs: 'handlebars', njk: 'nunjucks' }` (this is useful if you use different file extension naming conventions) * `engineSource` (Object) - the default template engine source, defaults to [@ladjs/consolidate][consolidate] * `locals` (Object) - locals to pass to templates for rendering * `cache` (Boolean) - defaults to `false` for `development` and `test` environments, and `true` for all others (via `process.env.NODE_ENV`), whether or not to cache templates * `pretty` (Boolean) - defaults to `true`, but is automatically set to `false` for subject templates and text-based emails * `message` (Object) - default [Nodemailer message object][nodemailer-message-object] for messages to inherit (defaults to an empty object `{}`) * `send` (Boolean) - whether or not to send emails, defaults to `false` for `development` and `test` environments, and `true` for all others (via `process.env.NODE_ENV`) (**NOTE: IF YOU ARE NOT USING `NODE_ENV` YOU WILL NEED TO MANUALLY SET THIS TO `true`**) * `preview` (Boolean or Object) - whether or not to preview emails using [preview-email][], defaults to `false` unless the environment is `development` (via `process.env.NODE_ENV`) – if you wish to disable the iOS Simulator then pass `{ openSimulator: false }` * `i18n` (Boolean or Object) - translation support for email templates, this accepts an I18N configuration object (defaults to `false`, which means it is disabled) which is passed along to [@ladjs/i18n][i18n] – see [Localization](#localization) example for more insight * `render` (Function) - defaults to a stable function that accepts two argument, `view` (String) and `locals` (Object) - you should not need to set this unless you have a need for custom rendering (see [Custom Rendering (e.g. from a MongoDB database)](#custom-rendering-eg-from-a-mongodb-database)) * `customRender` (Boolean) - defaults to `false`, unless you pass your own `render` function, and in that case it will be automatically set to `true` * `textOnly` (Boolean) - whether or not to force text-only rendering of a template and disregard the template folder (defaults to `false`) * `htmlToText` (Object) - configuration object for [html-to-text][] * `ignoreImage` (Boolean) - defaults to `true` * `subjectPrefix` (Boolean or String) - defaults to `false`, but if set to a string it will use that string as a prefix for your emails' subjects * `juice` (Boolean) - whether or not to use [juice][] when rendering templates (defaults to `true`) (note that if you have a custom rendering function you will need to implement [juice][] in it yourself) * `juiceResources` (Object) - options to pass to `juice.juiceResources` method (only used if `juice` option is set to `true`, see [juice's][juice] API for more information * `applyStyleTags` (Boolean) - defaults to `false` (as of v11, since modern browsers now support `