Attaching Remote Images to Emails Using Mailgun in Node.js

I’m working on a Node.js application and need help with email attachments. My setup includes Mailgun for sending emails, Cloudinary for image storage, and MongoDB for data persistence.

Here’s my current workflow:

  • Users upload photos through my web application
  • Images get stored on Cloudinary and URLs are saved in MongoDB
  • Emails are sent via Mailgun containing image links in the message body

The problem is that users have to click each link separately to view images. I want to include the images directly as email attachments instead.

I’ve checked Mailgun’s documentation but it seems like remote images can’t be attached directly. When I try using attachment parameters, I get file not found errors.

var imageUrls = [];
post.pictures.forEach(function(img){
    imageUrls.push(img.url);
});

var fileAttachment = new mailgun.Attachment({
    data: imageUrls[0], 
    filename: "photo.jpg"
});

var emailData = {
    from: "[email protected]",
    to: "[email protected]", 
    subject: "New Post Alert",
    html: "Check out this new post with attached images",
    attachment: fileAttachment
};

This throws an error:

Error: ENOENT: no such file or directory, stat 'https://res.cloudinary.com/myaccount/image/upload/photo.jpg'

Is there a way to attach remote images directly, or do I need to download them first?

mailgun dont support remote urls as attachments - u gotta download the images locally first. i use axios with responseType: ‘stream’ when pulling from cloudinary, then pipe it to a temp file. works well but itll slow down ur email sending a bit.

Mailgun wants actual file buffers or file paths, not remote URLs. I ran into this exact problem building a newsletter service that pulled images from AWS S3. Instead of downloading to disk, I just worked with memory buffers - way more efficient. Use node-fetch or axios to grab the image data directly into memory, then pass that buffer to Mailgun. Something like: const response = await fetch(imageUrls[0]); const buffer = await response.buffer(); const fileAttachment = new mailgun.Attachment({ data: buffer, filename: ‘photo.jpg’ }); This skips the hassle of writing temp files and cleaning them up. Just watch your memory usage with large images or high volume.

You’re getting this error because Mailgun’s attachment feature needs local file paths, not remote URLs. It’s trying to read the Cloudinary URL as a local file path, which doesn’t exist. I hit the same issue with SendGrid last year and had to download the files first. You’ll need to fetch the images from Cloudinary before attaching them. Here’s what worked for me: const https = require(‘https’); const fs = require(‘fs’); const path = require(‘path’); function downloadImage(url, filepath) { return new Promise((resolve, reject) => { const file = fs.createWriteStream(filepath); https.get(url, (response) => { response.pipe(file); file.on(‘finish’, () => { file.close(); resolve(filepath); }); }).on(‘error’, reject); }); } const localPath = await downloadImage(imageUrls[0], ‘./temp/photo.jpg’); const fileAttachment = new mailgun.Attachment({ data: localPath, filename: ‘photo.jpg’ }); Don’t forget to clean up those temp files or you’ll run into storage problems.