Search code examples
node.jsmulternodemailer

Nodemailer attachment from memory not working


I'm using multer and nodemailer to read in images from a form. I want to send those images as attachments in an email. (The use-case is that the user fills out a form to report an error and can upload screenshots of the error)

Here's my code:

// ...
const multer  = require('multer')
// const upload = multer(); // I believe the below is the same thing but more wordy
const upload = multer({storage: multer.memoryStorage()});

// ...
router.post('/reportissue', upload.array('screenshots[]', 3), reportIssue);

// ...
reportIssue = async function (req, res) {
     try {
          let { type, content } = req.body;

          let replyToEmail = /* default reply_to email address */ '[email protected]';
          let replyTo = false;
          let reporter_id = req?.user?.user_id;

          // get the user's email who's making the report
          if(reporter_id) {
               let user = null; // ... SQL to get user info here
               if(user?.email) {
                    replyToEmail = user.email;
                    replyTo = true;
               }

          }

          sendIssueReport({
               reply_to_email: replyToEmail, 
               reply_to: replyTo, 
               issue: content, 
               type: type, 
               reporter_id: reporter_id,
               attachments: req.files,
          });
          return res.status(200).send('issue received');
     } catch (err) {
          // ...
     }
     
// ...
sendIssueReport = async function (params) {
     try {
          const { reply_to_email, reply_to, issue, type, reporter_id, attachments } = params;
          
          let transport = exports.createTransport();
          const email = new Email({
               transport: transport,
               send: true,
               preview: false,
               views: {
                    options: {
                         extension: 'ejs',
                    },
                    root: 'app/email/templates',
               },
          });

          let result = await email.send({
               template: 'report_issue',
               message: {
                    from: process.env.NO_REPLY_EMAIL_ADDRESS,
                    to: /* support mailbox */ '[email protected]',
                    replyTo: (reply_to ?? false) ? reply_to_email : '[email protected]',
               },
               locals: {}, // locals go here
               // ***HERE***
               // Even though I've specified the attachments, they never come through.
               attachments: attachments.map(a => ({
                    filename: a.originalname,
                    content: a.buffer,
                    // contentType: a.mimetype, // this didn't appear to have an effect
                  })),
          });
          console.log(`issue email has been sent!`);
          return null; // no issue
     } catch (err) {
          // ...
     }
}

An output of attachments[0] in included below, in case that is helpful:

{
  fieldname: 'screenshots[]',
  originalname: 'audible_color.png',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 00 00 00 02 00 08 06 00 00 00 f4 78 d4 fa 00 00 00 04 67 41 4d 41 00 00 b1 8f 0b fc 61 05 00 ... 10905 more bytes>,
  size: 10955
}

I have also verified that the req.files are making it all the way into (and out of) the mapping function as intended. However, somewhere nodemailer is dropping the attachments, because they are not present in the sent email.

I've looked for existing solutions, but I can say unequivocally: I do NOT want to store the image. I WANT it to be kept in memory and forgotten as soon as the email is sent without any fuss.

I want to read in files (of any extension) and write those same files out as attachments in nodemailer.

Any help is appreciated.


Solution

  • In a frustrating turn of events... Operator error.

    The attachments object must go inside of the message object.

    // ...
    let result = await email.send({
         template: 'report_issue',
              message: {
                   from: process.env.NO_REPLY_EMAIL_ADDRESS,
                   to: /* support mailbox */ '[email protected]',
                   replyTo: (reply_to ?? false) ? reply_to_email : '[email protected]',
                   // ***HERE*** DO: Put attachments in the message object!
                   // Good:
                   attachments: attachments.map(a => ({
                        filename: a.originalname,
                        content: a.buffer,
                        contentType: a.mimetype,
                   })), // YES
              },
              locals: {}, // locals go here
              // DO ***NOT*** put attachments here. Bad:
              attachments: [/* ... */], // NO
         });
    // ...