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