forwardemail / email-templates

Create, preview (browser/iOS Simulator), and send custom email templates for Node.js. Made for @forwardemail, @ladjs, @cabinjs, @spamscanner, and @breejs.
https://forwardemail.net/docs/send-emails-with-node-js-javascript
MIT License
3.67k stars 337 forks source link

Using nodemailer embedded images #291

Closed rozsival closed 6 years ago

rozsival commented 6 years ago

According to https://nodemailer.com/message/embedded-images/ it should be possible to embed images from attachments. However, when using email-templates my attachment does not get embed.

At first I thought it might be caused by custom render I implemented, but when I tried using it with email-templates default render without any customization, it did not work too.

Any suggestions on what should I do to make it work?

mails/index.js

// @flow
import path from 'path';
import Email from 'email-templates';

import { environment, mail } from 'config';
import render from './render';
import { TEMPLATES } from './constants';

import type { Mail } from './types';

const production = environment === 'production';
const resources = path.resolve(__dirname, 'resources');

const email = new Email({
  send: production,
  preview: !production,
  message: {
    from: mail.from,
    attachments: [
      {
        filename: 'logo-sizeid.png',
        path: `${resources}/logo-sizeid.png`,
        cid: 'logo-sizeid',
      },
    ],
  },
  transport: {
    transportJson: !production,
    host: mail.host,
    port: mail.port,
    auth: {
      user: mail.user,
      pass: mail.pass,
    },
  },
  juiceResources: {
    preserveImportant: true,
    webResources: {
      relativeTo: resources,
    },
  },
  views: {
    root: TEMPLATES,
    options: {
      extension: 'hbs',
    },
  },
});

email.render = render(email);

export default (email: Mail);

mails/render.js

// @flow
import fs from 'fs-extra';
import cheerio from 'cheerio';
import handlebars from 'handlebars';
import layouts from 'handlebars-layouts';
import { Inky } from 'inky';

import { urls } from 'config';
import { TEMPLATES } from './constants';

handlebars.registerHelper(layouts(handlebars));
handlebars.registerPartial(
  'layout',
  fs.readFileSync(`${TEMPLATES}/layout.hbs`, 'utf8'),
);

export default (email: Object) => (view: string, locals?: Object): Promise<*> =>
  new Promise(async (resolve, reject) => {
    try {
      const { filePath } = await email.getTemplatePath(view);
      const source = await fs.readFile(filePath, 'utf8');
      const template = handlebars.compile(String(source));
      const compiled = template({
        ...locals,
        app: urls.app,
        year: new Date().getFullYear(),
        language: view.split('/')[0],
      });

      if (view.includes('subject')) return resolve(compiled);

      const inky = new Inky();
      const html = await email.juiceResources(
        inky.releaseTheKraken(cheerio.load(compiled)),
      );

      resolve(html);
    } catch (error) {
      reject(error);
    }
  });

mails/templates/layout.hbs

<!DOCTYPE html>
<html lang="{{language}}">
  <head>
    <meta http-equiv="Content-Type" content="text/html;utf-8">
    <link rel="stylesheet" href="styles.css" type="text/css" data-inline>
  </head>
  <body>
    <wrapper class="text-center">
      <spacer size="32"></spacer>
      <wrapper class="text-center">
        <a href="{{app}}">
          <img src="cid:logo-sizeid" alt="SizeID" class="logo">
        </a>
      </wrapper>
      <spacer size="16"></spacer>
      <wrapper class="text-center">
        <container class="message text-center">
          <div class="message__content">
            {{#block "body"}}{{/block}}
          </div>
        </container>
      </wrapper>
      <div class="footer">
        Copyright &copy; {{year}} SizeID.com All rights reserved.
        <br>
        SizeID s.r.o., Křížová 3324/4a, Prague 150 00, Czech Republic, EU
      </div>
    </wrapper>
  </body>
</html>

I need to get this to work as base64 encoding is a no-go for us. Everything else works without any issues.

rozsival commented 6 years ago

The solution is to set images: false inside juiceResources.webResources config. This should definitely be part of docs as it's handled by https://github.com/jrit/web-resource-inliner. Then the images are not inlined and attachment embedding works.

phaseOne commented 5 years ago

For those joining us now, note that #303 fixed this in v4.0.2:

niftylettuce commented on Jul 31, 2018

I've released v4.0.2 which sets images: false in the built-in configuration of email-templates. I also added a note to the README regarding this here https://github.com/niftylettuce/email-templates#plugins.

briantical commented 3 years ago

I am still failing to display my embedded images using cid. This is my current implementation.

const email = new Email({
  transport: smtpTransport,
  juice: true,
  juiceResources: {
    preserveImportant: true,
    webResources: {
      images: true,
      relativeTo: path.join(__dirname, '../emails/assets'),
    },
  },
});

const sendMail = async (template, receiver, options) => {
  await email.send({
    template,
    message: {
      to: receiver,
    },
    locals: {
      ...options,
    },
    attachments: [
      {
        filename: 'cube.png',
        path: path.join(__dirname, '../assets/cube.png'),
        cid: 'sbil',
      },
    ],
  });
};

and my img tag in pug img(src='cid:sbil', alt='SBIL', style='display:block;width:29px;height:auto;margin-left:auto;margin-right:auto;margin-bottom:10px', height='auto')