nodemailer, how to embed an image into an email
By Flavio Copes
Learn how to embed an image in a nodemailer email by reading it with fs.readFileSync, encoding it as a base64 data URI, and dropping it into the HTML body.
To embed an image into an email sent with nodemailer, you can encode the image as a base64 data URI and put it straight into the HTML body, or attach it with a cid and reference it from an img tag. Let’s see both, starting from the data URI approach I used.
I had the need to send an image to an email I was sending with nodemailer.
I tried using an attachment but.. the image was added as attachment. It showed up in the attachments list of the email client, not inside the message where I wanted it.
So I embedded the image as base64 into the email body.
Encoding the image as base64
First I added some imports:
import fs from 'node:fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
We need to do that __filename and __dirname stuff because with ES modules (import syntax) __dirname is not available and we have to reference a file with fs.readFileSync() but that wants absolute paths, not relative ones.
Long story short, do that.
Now read the image:
const imageData = fs.readFileSync(__dirname + '/image.jpg', 'binary')
Transform that into a base64-encoded string:
const src = `data:image/jpg;base64,${Buffer.from(
imageData,
'binary'
).toString('base64')}`
A data URI packs the whole image into a string, so the email is self-contained. No external hosting, no separate file. The downside is size: base64 makes the data about 33% bigger, so this works best for small images like logos or charts.
By the way, you can skip the 'binary' round trip: calling fs.readFileSync() without an encoding returns a Buffer, and you can call .toString('base64') on it directly.
Finally you can add that to the email body:
const mailOptions = {
//...
html: `<img style="width:800px;" src="${src}">`,
}
When the image doesn’t show up
Here’s the catch with data URIs: some email clients refuse to render them. Gmail is the most notable one, it strips base64 images from the src attribute, and your recipients see a broken image.
If that hits you, use a CID attachment instead. You still attach the image, but you give it a content id, and reference that id in the HTML:
const mailOptions = {
//...
html: '<img style="width:800px;" src="cid:report-chart">',
attachments: [
{
filename: 'image.jpg',
path: __dirname + '/image.jpg',
cid: 'report-chart',
},
],
}
The image still travels as an attachment, but the cid: reference tells the email client to display it inline in the body instead of listing it as a downloadable file. This is what was missing from my first attachment attempt, and it’s the approach that works across all the major clients.
Want me to talk about your product? You can sponsor this site.
Related posts about node: