7

I'm trying to send my stream data via email using Nodemailer but for some reason, the attachment is coming up as 0 kb when I download it and look at its info. How can I properly send the stream & it's data as an attachment? The stream should contain a PKPass is it a better option to send the response as an attachment? I'm using passkit-generator to generate the PKPass

const stream = examplePass.generate();

res.set({
    'Content-Type': 'application/vnd.apple.pkpass',
    'Content-disposition': `attachment; filename=${passName}.pkpass`,
});

stream.pipe(res);

//Send the receipt email

stream.on('finish', (attach) => {

    let transporter = nodemailer.createTransport({
        host: 'smtp.gmail.com',
        port: 587,
        secure: false,
        requireTLS: true,
        auth: {
            user: 'email4@gmail.com',
            pass: 'password',
        },
    });

    let mailOptions = {
        from: 'email4@gmail.com',
        to: 'emailTo1@gmail.com',
        subject: 'You\'re on your way to ',
        html: '<h1>Reciept email</h1>',
        attachments: [
            {
                filename: 'Event.pkpass',
                contentType: 'application/vnd.apple.pkpass',
                content: stream,
            },
        ],
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            return console.log(error.message);
        }
        console.log('success ' + info);
    });

});
Marian
  • 3,789
  • 2
  • 26
  • 36
user
  • 345
  • 2
  • 8
  • 32
  • 1
    Usually one needs to wait for the 'finish' event of the stream to fire before carrying on the next task. So once the finish event fires you can send the email, obviously promises or callbacks can be used. – Untimely Answers Jun 12 '20 at 15:43
  • @khan I have updated my question. Can you take a look I'm still not receiving the pass with any data. – user Jun 15 '20 at 00:01
  • I am no expert of stream but if `examplePass.generate()` returns a readable stream. I believe you could just set `content: stream`. I read from your question before edited you might already try that, if that's the case then this is not the solution. Here is a [reference](https://nodemailer.com/message/attachments/) on how to add attachments using `nodemailer`. – hangindev.com Jun 15 '20 at 01:22
  • @Hangindev I tried to set `content: stream` but it still didn't work. I've read through `nodemailer` documentation but can't get it to work with stream attachment. – user Jun 15 '20 at 01:40
  • @Hangindev when I log stream I get `[object Object]` is this a readable stream? – user Jun 15 '20 at 02:05
  • Do you have the implementation of `examplePass.generate()`? – hangindev.com Jun 15 '20 at 02:22
  • Yes, I think so, `const stream = examplePass.generate();` in my code is the implementation. Right? @Hangindev – user Jun 15 '20 at 02:29
  • I meant the code inside function `generate`. – hangindev.com Jun 15 '20 at 02:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215967/discussion-between-james-and-hangindev). @Hangindev – user Jun 15 '20 at 02:32
  • Can you see if something comes to the `data` event? I ask, because with regular streams created using `fs.createReadStream` your code works fine. – Rustam D9RS Jun 15 '20 at 08:47
  • @RustamD9RS When I call this function `stream.pipe(res)` the response on the client end has the `PKPass` attached as expected. Is this what you mean? – user Jun 15 '20 at 11:53

3 Answers3

5

It seems that in this case, the only way to send the file is to read the entire stream and send it as a string or buffer:

const stream = examplePass.generate();

res.set({
    'Content-Type': 'application/vnd.apple.pkpass',
    'Content-disposition': `attachment; filename=${passName}.pkpass`,
});

stream.pipe(res);

const chunks = [];

stream.on('data', chunk => {
    chunks.push(chunk);
});

stream.on('end', () => {
    const content = Buffer.concat(chunks);

    const transporter = nodemailer.createTransport({
        host: 'smtp.gmail.com',
        port: 587,
        secure: false,
        requireTLS: true,
        auth: {
            user: 'email4@gmail.com',
            pass: 'password',
        },
    });

    const mailOptions = {
        from: 'email4@gmail.com',
        to: 'emailTo1@gmail.com',
        subject: 'You\'re on your way to ',
        html: '<h1>Reciept email</h1>',
        attachments: [
            {
                filename: 'Event.pkpass',
                contentType: 'application/vnd.apple.pkpass',
                content
            },
        ],
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            return console.log(error.message);
        }
        console.log('success:', info);
    });

});

BUT! You need to be careful with this approach if the file is large, as it is fully loaded into RAM.

Rustam D9RS
  • 3,236
  • 1
  • 10
  • 17
  • The file is now around `300 KB` but the `PKPass` isn't valid on my response end unless I remove this line `stream.setEncoding('utf8');` and I'm receiving this message when I try to open the `PKPass` after downloading it from the sent email with and without the `encoding` line. `The pass "Event.pkass" could not be opened` any idea on why this could be? – user Jun 16 '20 at 05:15
  • I updated the code so that it uses a buffer to send content. Check it out, please. Maybe it will work. – Rustam D9RS Jun 16 '20 at 07:32
3

Your problem is that you piped the stream to res which is a "StreamReader" aka consumer, instead you need just to pass it to nodeMailer which it is a StreamReader by itself.

const stream = examplePass.generate();

// res.set({
//   'Content-Type': 'application/vnd.apple.pkpass',
//   'Content-disposition': `attachment; filename=${passName}.pkpass`,
// });

// stream.pipe(res); <- remove this

//Send the receipt email

stream.on('finish', (attach) => {
  let transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 587,
    secure: false,
    requireTLS: true,
    auth: {
      user: 'email4@gmail.com',
      pass: 'password',
    },
  });

  let mailOptions = {
    from: 'email4@gmail.com',
    to: 'emailTo1@gmail.com',
    subject: "You're on your way to ",
    html: '<h1>Reciept email</h1>',
    attachments: [
      {
        filename: 'Event.pkpass',
        contentType: 'application/vnd.apple.pkpass',
        content: stream,
      },
    ],
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log(error.message);
    }
    console.log('success ' + info);
  });
});

This is how I'm streaming email attachments.

felixmosh
  • 32,615
  • 9
  • 69
  • 88
-1

hi way i have this issues ? `file:///home/racal/JavaScript/Node/Web-Env/webpack.config.dev.js:10 path.resolve(__dirname, 'src/index') ^

ReferenceError: __dirname is not defined
at file:///home/racal/JavaScript/Node/Web-Env/webpack.config.dev.js:10:18
at ModuleJob.run (internal/modules/esm/module_job.js:138:23)
at async Loader.import (internal/modules/esm/loader.js:178:24)

`

NsdHSO
  • 124
  • 7