0

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: "sender@example.com" },
  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?

Aaron
  • 6,988
  • 4
  • 31
  • 48
  • Nodemailer have the option "attachments" to include a file into the email. I checked the document and it supports base64 format. It doesn't suit your case? – Đăng Khoa Đinh Apr 20 '21 at 17:25
  • It does! What I need to be able to do is extract the inline base64-encoded images into that attachments array. Currently they are inline in the email body. – Aaron Apr 20 '21 at 18:30
  • If the email body is an HTML string, then we can use some tools like https://www.npmjs.com/package/node-html-parser to parse it then use a query selector to grab the tag and get the attribute 'src'. Or we can use regex to match the part in the string. What's your opinion ? – Đăng Khoa Đinh Apr 20 '21 at 18:45
  • node-html-parser may help me do what I'm trying to do. I will give that a try tomorrow. Thank you for the lead. – Aaron Apr 20 '21 at 20:58

3 Answers3

1
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.

akisoft
  • 503
  • 6
  • 12
  • Thank you for this direction. I was hoping for some library that already does this, but you made me realize that the format of these images is pretty predictable. I'm sure I can extract them with a simple regex. I'll give that a try. – Aaron Apr 21 '21 at 14:06
  • You helped me think about my problem in a different way, so thank you for that. – Aaron Apr 21 '21 at 18:03
  • 1
    you are welcome my friend, we are here to help one another – akisoft Apr 21 '21 at 18:11
1

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.

Aaron
  • 6,988
  • 4
  • 31
  • 48
0

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

 const emailRequest = {
      from: { name: "Sender name", address: "sender@example.com" },
      to: [/* recipients */],
      subject: "Email subject",
      attachment: [{
         path: filePath, //"public/data/somefile.png"
         filename: filename,
         cid: filename + "@"
      }],
      text: "Email text",
      html: "Email HTML",
    } as SendMailOptions;
Syed Mohib Uddin
  • 718
  • 7
  • 16
  • Yes! This is how I hope to attach the images, but before I can do that I need to extract the image data from the existing email body. – Aaron Apr 20 '21 at 20:47
  • What do you meant by you need to extact image data from existing email body do you want to send image via mail or need to get image from mail? – Syed Mohib Uddin Apr 20 '21 at 20:51
  • I updated the question just now. I need to send the email, but I need to remove the inline images in the email and add them as attachments instead. – Aaron Apr 20 '21 at 20:57
  • You need to send data from file edited the answer – Syed Mohib Uddin Apr 20 '21 at 21:01