Move inline images to attachments with nodemailer

2.1k views Asked by At

I have a node application successfully sending emails using nodemailer. I am sending emails with code similar to this:

const emailRequest = {
  from: { name: "Sender name", address: "[email protected]" },
  to: [/* recipients */],
  subject: "Email subject",
  text: "Some email text",
  html: '<p>Some email text</p><p><img src="data:image/png;base64,{base64_image_content}">',
} as SendMailOptions;

const ses = new SES();
const transporter = nodemailer.createTransport({
  SES: ses,
  sendingRate: MAX_SEND_RATE,
});

transporter.sendMail(emailRequest, sendMailResponseHandler);

The problem is that sometimes the email body contains inline images formatted like this, where {base64_image_content} is the actual data containing the image:

<img src="data:image/png;base64,{base64_image_content}">

and Gmail does not support inline image data. The email sends and the image data shows up when I choose "Show original" in Gmail, but the image does not appear in the Gmail web client and it appears as a broken image in the Gmail mobile client.

I don't have control over the inline images, so I'm hoping to find a way to extract the inline image data and add it as an attachment to the email, and use a cid to refer to those attachments in the email body before I call transporter.sendMail.

I want to change the HTML from this:

<p>Some email text</p><p><img src="data:image/png;base64,{base64_image_content}">

to this:

<p>Some email text</p><p><img src="cid:myImageCid">

and add the image as an attachment:

const emailRequest = {
  // other things
  attachments: [{ cid: "myImageCid", content: base64_image_content }]
} as SendMailOptions;

The part I need help with is extracting the image data from the original email HTML and replacing it with a cid reference. Is there a way I can do that?

3

There are 3 answers

0
Aaron On BEST ANSWER

I ended up following the advise of akisoft by extracting the image data myself. Here's how I did it:

extractImages = (html: string) => {
  // Match all image tags with data in their src
  const regex = /<img src="data:.+?">/gs;
  const images = html.match(regex) ?? [];
  const attachments = images.map((originalImageTag) => {
    // Parse out the image data and meta info
    const imageSrc = (originalImageTag.match(/<img src="data:(.+?)">/s) ?? [])[1];
    const [imageMeta, content] = imageSrc.split(",");
    const [imageType, encoding] = imageMeta.split(";");
    const fileType = imageType.split("/")[1];
    const filename = `image.${fileType}`;
    const cid = uuid();
    const newImageTag = `<img src="cid:${cid}">`;

    // Replace the image tags with ones that reference attached content
    html = html.replace(originalImageTag, newImageTag);

    // Collect attachment data to be passed to nodemailer
    return {
      filename,
      cid,
      encoding,
      content,
      contentDisposition: "inline",
    } as Attachment;
  });

  // Return the modified HTML and the attachments
  return { html, attachments };
};

I pass the HTML of the original email into that function and it returns modified HTML and a list of attachments I can pass directly to nodemailer.

3
akisoft On
require("fs").writeFile("out.png", (some_email_html.split("img")[1]).replace('">',"").split(",")[1], 'base64', function(err) {

console.log(err); 

});

the code above will extract an image from the html assuming some_email_html as the body of the mail, and save it as out.png, you can change the name to anything else, and delete when the email as been sent. Note that the code assume you are expecting a single image, you can modify to extract more, or let me know if you need help with that as well.

4
Syed Mohib Uddin On

Send attachment in mail like this you can provide attachment in request.

 const emailRequest = {
      from: { name: "Sender name", address: "[email protected]" },
      to: [/* recipients */],
      subject: "Email subject",
      attachment: [{
         path: filePath, //"public/data/somefile.png"
         filename: filename,
         cid: filename + "@"
      }],
      text: "Email text",
      html: "Email HTML",
    } as SendMailOptions;