0

I am using React with Nodemailer to send emails from a user input form, the user should be able to attach a file (a PDF for example) through the form and the content of the form will be sent as an email using Nodemailer. My issue comes with not knowing how to attach the file to the email. Here is a list and examples of properties that can be used using Nodemailer. What properties can I extract from the object inputted through the file input to the event.target.files to use to attach to the email, can I get the path of the inputted file for example?

Code:

const [file, setFile] = useState(null);

const handleSubmit = async(e) => {
e.preventDefault();

try {
  await axios.post("http://localhost:4000/send_form", { file });
}
catch (error) {
  console.log(error);
}
}

return (
  <form onSubmit={handleSubmit}>
    <input
      type="file"
      onChange={(e) => setFile(e.target.files[0])}
      required/>
    <button type="submit">Send</button>
  </form>
);

Server:

app.post("/send_form", cors(), async (req, res) => {
  let { file } = req.body;

  await transport.sendMail({
    from: "from@sender.com",
    to: "to@receiver.com",
    subject: "Subject",
    html: `<h1>Hello</h1>`,
    attachments: [{
      filename: "",
      path: ""
    }]
  })
});
random1234
  • 777
  • 3
  • 17
  • 41
  • 1
    You've probably got at least three different problems here. (1) That isn't how you upload files with axios. (2) That isn't how you handle uploaded files in express. (3) Whatever the problem with nodemailer is – Quentin Apr 01 '22 at 09:26
  • What specifically am I doing wrong? – random1234 Apr 01 '22 at 09:30
  • https://www.google.com/search?client=firefox-b-d&q=site%3Astackoverflow.com+how+do+I+upload+files+with+axios – Quentin Apr 01 '22 at 09:35
  • https://www.google.com/search?client=firefox-b-d&q=site%3Astackoverflow.com+express.js+handle+file+uploads – Quentin Apr 01 '22 at 09:35
  • you can change the attachment into buffer and send it like that. – Alim Öncül Apr 10 '22 at 20:19

1 Answers1

2

You don't need axios to upload the file, just POST it as FormData with the Fetch API.

async function handleSubmit(event) {
  event.preventDefault();

  let fd = new FormData();
  fd.append('myfile', file);

  fetch('http://localhost:4000/upload', {
    method: 'POST', body: fd
  }).catch(err => {
    console.error(err);
  });
}

If you have other data to submit along with your image it can also be append to the FormData object.

fd.append('name', 'Joe');
fd.append('age', 40);
// etc...

Or, you can simply capture all fields from any HTMLFormElement. Just make sure to set the enctype attribute to be multipart/form-data.

let form = document.querySelector('#your-form');
let fd = new FormData(form);

Then, on the server you can use the multer middleware to stream the file buffer to the nodemailer attachment:

import express from 'express';
import multer from 'multer';
import transport from './your_app.js'

const app = express();

const upload = multer({
  storage: multer.memoryStorage()
});

app.post('/upload', upload.single('myfile'), (req, res) => {
  transport.sendMail({
    from: "from@sender.com",
    to: "to@receiver.com",
    subject: "Subject",
    html: `<h1>Hello</h1>`,
    attachments: [{
      filename: req.file.originalname,
      content: req.file.buffer
    }]
  })
});

app.listen(4000);

If you have other middleware you need to use on this route, they can be passed in as an array:

import cors from 'cors';

let middleware = [
  cors(),
  upload.single('myfile')
];

app.post('/upload', middleware, handler);

Note that the key used in the following two statements must match. This key corresponds to the name attribute of the file input.

In handleSubmit() :

fd.append('myfile', file);

In app.post() :

upload.single('myfile')

Multer also allows for multiple file uploads if needed. You can either capture several files from a single input with the multiple attribute:

upload.array('myfile', 3)

Or you could use several file inputs:

upload.fields([
  { name: 'myfile', maxCount: 1 },
  { name: 'another-file', maxCount: 8 }
])

If you do this, you will need to access the uploaded file data from the req.files property instead of the singular req.file.

The rest of your form data will be available in the req.body object:

req.body.name == 'Joe'
req.body.age == 40;
Besworks
  • 4,123
  • 1
  • 18
  • 34
  • Thank you for your answer, I will try this solution, but a question first, what if I have multiple values sent to the server from my form, do I still retrieve them in the parameters of the `app.post()` method? How would I go about doing this, do I use `upload.single("")` for every value, or is there a different way? – random1234 Apr 10 '22 at 17:39
  • Also you removed the `cors()` method call from your `app.post()` parameter. – random1234 Apr 10 '22 at 18:08
  • 1
    I've updated my answer to address the points made in your comments. – Besworks Apr 10 '22 at 20:13
  • Hey, thank you for the edit and for the help, I tried your middleware array solution, however I am now getting the Cross-origin request blocked, are you sure it should work like this? Or may I be doing something incorrectly? – random1234 Apr 11 '22 at 16:09
  • Passing in the middleware as an array [should definitely work](https://github.com/expressjs/express/issues/3237). Did you remember to `import cors from 'cors'` first? – Besworks Apr 11 '22 at 17:25
  • I just tried it again without changing anything and weirdly enough it worked, perhaps the server was not running. – random1234 Apr 11 '22 at 17:52
  • Thank you my friend for your help and patience, appreciate it greatly! – random1234 Apr 11 '22 at 17:52
  • 1
    Happy to help! The rep boost is worth the extra effort. Good luck with the rest of your project. – Besworks Apr 11 '22 at 18:28