DEV Community

Cover image for How to implement email functionality with Node.js, React.js, Nodemailer, and OAuth2

How to implement email functionality with Node.js, React.js, Nodemailer, and OAuth2

Jared Long on March 30, 2021

Nodemailer is a Node.js module that allows users to send messages directly to your email. This article is a guide to help you connect your...
Collapse
 
oicydwa profile image
Chris

This is the best tutorial I have found for reactjs and nodemailer. Thank you for this.

I went through the comments and I saw that you did a followup for someone with react-hook-form. I also am using react-hook-form, and it helped me adapt to my code. However, I am attempting to use my form to send files through nodemailer. No matter where I look, I cannot find any information on getting the file data from react-hook-form into nodemailer. Would you happen to have any insight on this?

Collapse
 
jlong4223 profile image
Jared Long • Edited

Hey not sure if you've already figured out the react portion of this question but with react-hook-form and files, all you'd have to do is add an input like this to your form:

<input type="file" {...register("file")} />
Enter fullscreen mode Exit fullscreen mode

If you throw a console.log of the data in your onSubmit, you should see the file info there. I'll follow up later on the nodemailer side!

Collapse
 
oicydwa profile image
Chris • Edited

I have that part figured out already. I can get the data to my API, but what I can't figure out is how to make the 'file' attach with nodemailer. In my browser I see the file being sent like this:
"documentsUpload: FileList
0: File {name: 'Email Attachment test.txt', lastModified: 1647400270450, lastModifiedDate: Tue Mar 15 2022 22:11:10 GMT-0500 (Central Daylight Time), webkitRelativePath: '', size: 20, …}
length: 1"
But the data sent is blank:
"documentsUpload: { '0': {} }"
And nothing comes through on the email attachments.

Thread Thread
 
jlong4223 profile image
Jared Long • Edited

Yeah I'm actually running into the same thing. I can see the file updating when I log it but when I send it, it goes through blank. Not super sure why that's happening yet. I'm going to try and see If throwing together a custom onChange for this file input helps. Not sure what's happening in the background of react-hook-form with sending files.

Thread Thread
 
oicydwa profile image
Chris • Edited

I actually got this figured out LATE last night. It was NOT a simple solution and was very picky.

Thread Thread
 
jlong4223 profile image
Jared Long

That’s awesome! You mind sharing? Really curious to see what you came up with

Thread Thread
 
oicydwa profile image
Chris • Edited

This works with multi-file attachments too.

<Input
   type="file"
   ref={register}
   multiple
   {...register('file')}
   name={'file'}
   onChange={handleChange}
/>
Enter fullscreen mode Exit fullscreen mode

My handleChange()

const handleChange = async (e) => {
    setValue('file', e.target.value.files, {shouldDirty: true});
  }
Enter fullscreen mode Exit fullscreen mode

So, in my API calling method I did:

const fileUpload = require("express-fileupload"); // I don't know why
  app.use(express.urlencoded({ extended: true })); // But this is required. I dropped it
  app.use(fileUpload({
    createParentPath: true                        // and it stopped working.
  }));

 const sendEmail = async (data) => {
    let form = document.querySelector('form');
    const formData = new FormData(form);

    const {file} = data;

    for (let [index, value] of Object.entries(file)){
      formData.append(value.name, file[index]);
    }

    axios({
      method: 'post',
      url: 'http://localhost:3001/send',
      data: formData,
    });
  }
Enter fullscreen mode Exit fullscreen mode

and on the backend:

app.post('/send', async (req, res) => {

  const docs = Object.keys(req.files).map(key => {
    return {
      filename: req.files[key].name,
      content: req.files[key].data
    }
  });

  let mailOptions = {
    from: 'whoever@whereever.net',
    to: 'to-mail@mail.go',
    subject: `Message from: Whoever`,
    html: '<html></html>',
    attachments: docs // <--- All you need here!!
  };

  transporter.sendMail(mailOptions, function (err, data) {
    if (err) {
      res.json({
        status: 'fail',
      });
    } else {
      console.log('== Message Sent ==');
      res.json({
        status: 'success',
      });
    }
  });
});
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
jlong4223 profile image
Jared Long

Nice! Solid work. Glad you figured it out and thanks for sharing

Thread Thread
 
oicydwa profile image
Chris

I edited it a dozen times to make sure I had it just right, for posterity. I really hope this helps someone later, because it was a pain to figure out, and searching yielded bits and pieces, but NOTHING on the backend portion.

Thread Thread
 
jlong4223 profile image
Jared Long

Yeah it definitely will. I had a hard time finding quality full stack nodemailer stuff before putting this article together with bits and pieces that I found that worked. What really surprised me is how you were able to receive the file data on the api without needing a middleware package like multer. I’ll for sure be trying this.

Thread Thread
 
oicydwa profile image
Chris

I had to update. I took a part out I thought was no longer needed, and I was wrong. I guess there is your middleware you were talking about.

Thread Thread
 
vipcode786 profile image
VipCode786

Thanks @chris and @jlong4223 for sharing this information.

Collapse
 
jlong4223 profile image
Jared Long • Edited

Hey, really happy to hear that this helped you! This gets a little more complex and I'll definitely expand on this specific question with react-hook-form when I get a little more time.

Until then, these resources may help:

You will most likely have to use FormData() to handling attaching a file on the frontend.

Didn't test these comments but this stackoverflow article about FormData & react looks promising on how to do it with a handleChange or handleSubmit function.

On the backend, you will will have to at least use a library like multer to handle those file requests.

I know Vue is very different but I have a Vue.js frontend project where I sent a picture file with FormData():
github.com/jlong4223/the-office-cl...

This is another API of mine where I use multer to handle file requests. In this case, pictures:
github.com/jlong4223/node-cloudina...

I will get back to this, but until this hopefully these help a little 😅

Collapse
 
jordim21 profile image
Jordi Mantilla

Jared thank you so much for such an amazing tutorial, but i am still wondering, when you make the api call on the react side, you make a post to the url localhost:3001, it doesn't mean that the project is only gonna work when i have it on localhost? thank you so much!

Collapse
 
jlong4223 profile image
Jared Long

Glad that it helped :) this will work on localhost or if you deploy your api and hit the deployed url. Either way!

Collapse
 
shakes103 profile image
Onyema O.

Hi Jared Long, thank you so much for this post. I'm trying to use this process to build a Contact Us form on my NextJs website. I've walked through the tutorial but I'm unable to go beyond running the cURL command;

curl -d -url localhost:3001/send

I keep running into an error:

Invoke-WebRequest : A parameter cannot be found that matches parameter name 'url'.
At line:1 char:9

  • curl -d -url localhost:3000/send
  • ~~~~
    • CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
    • FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.InvokeWebRequestComman d

What do I need to do to go past this level?

Collapse
 
jlong4223 profile image
Jared Long

Hmm I think you could try adding the http:// to the beginning of the url:
curl -d -url http://localhost:3001/send

Collapse
 
jlong4223 profile image
Jared Long • Edited

If that doesn't work, you also don't have to use cUrl and could try hitting the route with Postman if you have it.
Or if you use VSCode, you could install the ThunderClient Extension for testing API routes directly inside of VSCode. marketplace.visualstudio.com/items...

Thread Thread
 
shakes103 profile image
Onyema O.

Okay. I will try this instead and see how it turns out. Thanks!

Collapse
 
lokessh04 profile image
LOKESSH

Hello Jared, this is one of the tutorials that worked flawlessly for me. I'm still extremely new to ReactJS and am developing a dashboard that uses triggers to send emails to the employees. I'm testing your tutorial separately before implementing in my actual project. My question is, do I have to manually start the server.js file every time I run the project in order for the email to go through? Or is there some other alternative to like automate it? Would love to hear your thoughts on it.

Collapse
 
jlong4223 profile image
Jared Long

Hey! Great to hear that this still works as expected and helped you! If you're doing everything locally, you will have to manually start the server file with node or nodemon. I think you'd probably want to deploy your api so that it doesn't have to manually run.

Collapse
 
lokessh04 profile image
LOKESSH

I see, alright then. Will try that out! Thank you for your tutorial and comment once again.

Collapse
 
bawbaw profile image
Naomi Mwangi

Thanks!! This works like a dime!

Collapse
 
kpierrelys profile image
Kpierrelys

Hey Jared, love this blog post! It was very helpful. Is there any configuration changes I need to do when deploying a project after implementing this? I'm deploying a project to heroku using this. Let me know! thanks

Collapse
 
jlong4223 profile image
Jared Long

Hey thanks for checking it out and glad it helped! Everything should work as expected . Just don't forget to set your env variables for heroku :)

Collapse
 
kpierrelys profile image
Kpierrelys

Thanks Jared I was able to deploy 🙌🏽

Thread Thread
 
juansebastianm profile image
Juan Sebastián Mendoza

Hey! could you please tell me how you deployed it to Heroku?
I have the frontend on Netlify and when I try to deploy all the code related to express, nodemailer and all that, the deployment fails.
stackoverflow.com/questions/704422...

Thread Thread
 
kpierrelys profile image
Kpierrelys

Hey Juan I am not to familiar with netlify but heres is a blog post breaking it down: dev.to/lindakatcodes/a-beginners-g...

Let me know if that works.

There is also another way to deploy your server (node.js). I haven't tried this way yet but this way seems a lot less complicated. Check out this video for the step by steps: youtu.be/ENrzD9HAZK4 fast forward to 14:55 where he starts explaining how to deploy your server.

Hope this helps!

Collapse
 
bulegahassan profile image
BulegaHassan

Thanks @jlong4223 , it worked for me but recently I have got an error after starting the server.

Server is running on port: 3001
Error: invalid_grant: Bad Request
at C:\Users\hassan_bulega\Desktop\repo_folder\nodemailerAPI\node_modules\nodemailer\lib\xoauth2\index.js:272:33
at PassThrough.<anonymous> (C:\Users\hassan_bulega\Desktop\repo_folder\nodemailerAPI\node_modules\nodemailer\lib\xoauth2\index.js:341:20)
at Object.onceWrapper (node:events:631:28)
at PassThrough.emit (node:events:517:28)
at endReadableNT (node:internal/streams/readable:1368:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'EAUTH',
command: 'AUTH XOAUTH2'

Collapse
 
bulegahassan profile image
BulegaHassan

@jlong4223 When I get a new refresh token, it works but this solution is not viable . How can I set this refresh token to have no expiry or a very long expiry date ?

Collapse
 
techspark_55 profile image
Md Nasir Uddin

I get an Error: I followed all the step when I am trying to veryfiy. it shows error: "Error: invalid_request: client_secret is missing." i have consoled log my client secret and it was there. any help please.

Collapse
 
jlong4223 profile image
Jared Long

Hey happy to try and help. What does your code look like?

Collapse
 
techspark_55 profile image
Md Nasir Uddin

**import express from "express";
import expressAsyncHandler from "express-async-handler";
import { google } from "googleapis";
import nodemailer from "nodemailer";
import dotenv from "dotenv";

dotenv.config();
const testRouter = express.Router();
// eslint-disable-next-line no-undef
const client_id = process.env.GMAIL_CLIENT_ID;
// eslint-disable-next-line no-undef
const client_secret = process.env.GMAIL_CLIENT_SECRET;
// eslint-disable-next-line no-undef
const redirect_uri = process.env.GMAIL_REDIRECT_URI;
// eslint-disable-next-line no-undef
const refresh_Token = process.env.GMAIL_REFRESH_TOKEN;
// eslint-disable-next-line no-undef
const myEmail = process.env.EMAIL;
// eslint-disable-next-line no-undef
const outLookEmail = process.env.OUTLOOKEMAIL;
// eslint-disable-next-line no-undef
const outLookPass = process.env.OUTLOOKPASS;

console.log(client_secret);

const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uri
);
oAuth2Client.setCredentials({
refresh_token: refresh_Token,
});

testRouter.post(
"/email",
expressAsyncHandler(async (req, res) => {
async function send_mail() {
const access_token = await oAuth2Client.getAccessToken();
const transport = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: myEmail,
clientId: client_id,
clinet_secret: client_secret,
refreshToken: refresh_Token,
accessToken: access_token,
},
tls: {
rejectUnauthorized: false,
},
});
// there is problem with this approach when verifying the transport is says clinent secret is missing;
transport.verify((err, success) => {
err
? console.log(err)
: console.log(=== Server is ready to take messages: ${success} ===);
});
const mailOptions = {
from: RareCarry ${myEmail},
to: "example123@gmail.com",
subject: "Your Order has been placed! ",
// attachment: attch,
text: Hello ${name} your order ID: . Thank you for shopping with us!. we will send your purchase details & invoice soon.,
html: <h2>Hello, ${name}</h2>
<h3>Your order ID:</h3>
<p>Thank you for shopping with us!. we will send your purchase details & invoice soon</p>
<em>Please visit our blog and news to get a discount deal. <a href='https://rarecarry.com'>RareCarry</a></em>
,
};
transport.sendMail(mailOptions, (error, result) => {
if (error) {
console.log("Error:", error);
} else {
console.log("Success: ", result);
}
transport.close();
});
}
send_mail();
res.send({ message: "Hi!" });
})
);

testRouter.post(
"/outlook",
expressAsyncHandler(async (req, res) => {
const transport = nodemailer.createTransport({
service: "hotmail",
auth: {
user: outLookEmail,
pass: outLookPass,
},
tls: {
rejectUnauthorized: false,
},
});
const mailOptions = {
from: RareCarry ${outLookEmail},
to: "careerit29@gmail.com",
// cc:'careerit29@gmail.com',
subject: "Your Order has been placed! ",
// attachment: attch,
text: Hello Nasir your order ID: . Thank you for shopping with us!. we will send your purchase details & invoice soon.,
html: <h2>Hello, Nasir</h2>
<h3>Your order ID:</h3>
<p>Thank you for shopping with us!. we will send your purchase details & invoice soon</p>
<em>Please visit our blog and news to get a discount deal. <a href='https://rarecarry.com'>RareCarry</a></em>
,
};

transport.sendMail(mailOptions, (err, result) => {
  if (err) {
    console.log(err);
  } else {
    console.log("Message sent:", result);
  }
});
transport.close();
res.send({ message: "HI!!!" });
Enter fullscreen mode Exit fullscreen mode

})
);

export default testRouter;**

Thread Thread
 
jlong4223 profile image
Jared Long • Edited

Awesome thanks. One thing that I see is that your client secret key is misspelled within your createTransport object but I think it should be camelCase as well. Hopefully that does the trick :)

clinet_secret: client_secret -> clientSecret: client_secret

and/or if you wanted to get fancy with new es6 syntax (not necessary at all):

define your client secret camelCase as well
const clientSecret = process.env.GMAIL_CLIENT_SECRET;

and within you transporter object, you could just define like so (could do with most of your variables):

const transport = nodemailer.createTransport({
service: "gmail",
auth: {
      type: "OAuth2",
      user: myEmail,
      clientId: client_id,
      clientSecret,                              <-------------------
      refreshToken: refresh_Token,
      accessToken: access_token,
},
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
techspark_55 profile image
Md Nasir Uddin

Thank you so much Jared, it's work. Great tutorial.

Collapse
 
rasah673 profile image
RaSah673

Hi Jared, thank you for this tutorial, it is very usefull for beginners like me.

I tried it, the server.js functions as you instructed. however, my App.js gives error at the first testing step.

Could you please, share the final view of App.js code please?

Many thanks,

Collapse
 
jlong4223 profile image
Jared Long

Yeah for sure. Here is the entire App.js file:

import { useState } from "react";

function App(props) {
  const [mailerState, setMailerState] = useState({
    name: "",
    email: "",
    message: "",
  });

  function handleStateChange(e) {
    setMailerState((prevState) => ({
      ...prevState,
      [e.target.name]: e.target.value,
    }));
  }

  const submitEmail = async (e) => {
    e.preventDefault();
    console.log({ mailerState });
    await fetch("http://localhost:3001/send", {
      method: "POST",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify({ mailerState }),
    })
      .then((res) => res.json())
      .then(async (res) => {
        const resData = await res;
        console.log(resData);
        if (resData.status === "success") {
          alert("Message Sent");
        } else if (resData.status === "fail") {
          alert("Message failed to send");
        }
      })
      .then(() => {
        setMailerState({
          email: "",
          name: "",
          message: "",
        });
      });
  };

  return (
    <div className="App">
      <form
        style={{
          display: "flex",
          height: "100vh",
          justifyContent: "center",
          alignItems: "center",
        }}
        onSubmit={submitEmail}
      >
        <fieldset
          style={{
            display: "flex",
            flexDirection: "column",
            justifyContent: "center",
            width: "50%",
          }}
        >
          <legend>React NodeMailer Contact Form</legend>
          <input
            placeholder="Name"
            onChange={handleStateChange}
            name="name"
            value={mailerState.name}
          />
          <input
            placeholder="Email"
            onChange={handleStateChange}
            name="email"
            value={mailerState.email}
          />
          <textarea
            style={{ minHeight: "200px" }}
            placeholder="Message"
            onChange={handleStateChange}
            name="message"
            value={mailerState.message}
          />
          <button>Send Message</button>
        </fieldset>
      </form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jlong4223 profile image
Jared Long • Edited

Is it an error when trying to run/start your client side? Or is it an error when trying to send an email?

Collapse
 
andrewbaisden profile image
Andrew Baisden

This is a good walkthrough thanks for sharing.

Collapse
 
arejasverduras profile image
Arejas • Edited

Hi Jared,
Thank you very much for this great tutorial, it seems like a great workable solution for contact forms and after learning react and node, a great first project for a working react app.

Everything works great locally.
However, getting the app to work in heroku is a no-no.. I think the problem is related to the package.json file.
After pushing to heroku the server.js files starts, however the app does not load (nothing loads).
After trying to push changes to heroku i keep getting a git-push-error-pre-receive-hook-declined

two things of my concern:

  • In the first step of creating the React app, i've created the app WITHIN the nodemailerAPI folder. This eventually leads to having two package.json files. Was this supposed to be this way? See the image with the folder structure.
  • i might have messed up the package.json file(s) by trying to create them using npm init, prior to deploying to heroku. Could you post how both of them should look like? I can update the versions of the dependencies ofcourse.

What i fail to understand is how the react app will actually gets build.. the procfile on heroku only specifies server.js

Please forgive my noobiness on this project :) hope you can help me out!

I will post the package.json code below:

I hope you can help me out!

Thanks!
Arejas

outer package.json (from nodemailerAPI
{
"name": "arejoreactcontact",
"version": "1.0.0",
"description": "contactreactapp",
"main": "server.js",

"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"express": "^4.17.3",
"nodemailer": "^6.7.3",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},

"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},

"devDependencies": {},
"repository": {
"type": "git",
"url": "git+github.com/arejasverduras/reactnod..."
},
"bugs": {
"url": "github.com/arejasverduras/reactnod..."
},
"homepage": "github.com/arejasverduras/reactnod..."
}

package.json inner (react app nodemailer-form)
{
"name": "nodemailer-form",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Collapse
 
jlong4223 profile image
Jared Long

Hey no problem at all! Deploying can be its own beast. I'd definitely recommend separating the two projects from each other, especially when deploying to Heroku. React has to go through a special deployment/build process for Heroku.

To deploy a react app to heroku, run this:

heroku create appName-here --buildpack mars/create-react-app
Git push heroku master
Enter fullscreen mode Exit fullscreen mode
Collapse
 
arejasverduras profile image
Arejas

Thank you Jared! I guess i will try to build some other react apps first, keep getting all kinds of errors. Trying to be ahead of my lurning curve i guess.. Thanks for the response and the tutorial, i will pick this project up later and deploy it!

Collapse
 
brzezinskibartosz profile image
brzezinskibartosz • Edited

Hi Jared, thanks for sharing this tutorial. On my website i'm using react hook form. After implementation bigger part of your code, input boxes don't allow me type in anything. For test i didn't set value as {mailerState.yourName} for other inputs. I can send email but it's empty. Please take a look for image in attachment.
Do you know what could be wrong? i'm pasting part of my code. I'll be very grateful for any hint. Cheers!

const { register, handleSubmit, formState: { errors }  } = useForm({
    mode: 'onBlur',
});


const [mailerState, setMailerState] = useState({
    yourName: "",
    email: "",
    phone: "",
    url: "",
    message: "",
});

function handleStateChange(e) {
    setMailerState((prevState) => ({
        ...prevState,
        [e.target.yourName]: e.target.value,
        [e.target.email]: e.target.value,
        [e.target.phone]: e.target.value,
        [e.target.url]: e.target.value,
        [e.target.message]: e.target.value,
    }));
}


const submitEmail = async(e) => {
    e.preventDefault();
    console.log({ mailerState });
    const response = await fetch("http://localhost:3001/send", {
        method: "POST",
        headers: {
            "Content-type": "application/json",
        },
        body: JSON.stringify({ mailerState }),
    })
    .then((res) => res.json())
    .then(async (res) => {
        const resData = await res;
        console.log(resData);
        if (resData.status === "success") {
            console.log("Message Sent");
        } else if (resData.status === "fail") {
            console.log("Message failed to send");
        }
    })
    .then(() => {
        setMailerState({
            yourName: "",
            email: "",
            phone: "",
            url: "",
            message: "",
        });
    });
};


return (
    <ContactsContainer lightBg={lightBg} id='contact'>
        <TextWrapper>
            <TopLine>CONTACTS</TopLine>
            <Heading>Heading</Heading>
            <Subtitle>Text</Subtitle>
        </TextWrapper>
        <form onSubmit={handleSubmit(submitEmail)}>
            <Column1 className="column1">
                <FormGroup className="formGroup">
                    <label className={errors.yourName ? "notok" : "ok"}>Your Name</label>
                    <input className={errors.yourName ? "notok" : "ok"}
                        type="text"
                        onChange={handleStateChange}
                        name="yourName"
                        value={mailerState.yourName}
                        {...register("yourName", { 
                            required: true,
                            minLength: 5 })}
                    />
                    {errors.yourName?.type === 'required' && (<p> 
                        Your name is required.</p>
                    )}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jlong4223 profile image
Jared Long • Edited

@brzezinskibartosz
Hey yeah totally. So react-hook-form actually makes things a little easier and requires less code. You actually wont need to specify a handleChange function as react-hook-form will handle that for you & you also wont need the useState hook involved.

Here's a few things you'll need to try:

  • update your input - remove the value & onChange:
 <input
     placeholder="Name"
     name="name"
     {...register("name", { required: true, minLength: 5 })}
  />
Enter fullscreen mode Exit fullscreen mode
  • update your submitEmail function - remove the mailerState being sent and update the parameter name to anything else (named mine data in this example) & e.preventDefault as react-hook-form actually does that for you as well and will throw an error if you leave it. not sure how to reset the values after submitting - youll probably want to check the docs
  async function submitEmail(data) {
    await fetch("http://localhost:3001/send", {
      method: "POST",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify({ data }),
    })
      .then((res) => res.json())
      .then(async (res) => {
        const resData = await res;
        console.log(resData);
        if (resData.status === "success") {
          alert("Message Sent");
        } else if (resData.status === "fail") {
          alert("Message failed to send");
        }
      });
  }
Enter fullscreen mode Exit fullscreen mode
  • update the API to look for the data being sent in the req instead of mailerState
  let mailOptions = {
    From: `${req.body.data.email}`,
    to: process.env.EMAIL,
    subject: `Message from: ${req.body.data.email} (${req.body.data.name})`,
    text: `${req.body.data.message}`,
  };
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brzezinskibartosz profile image
brzezinskibartosz • Edited

Hi Jared, actually i didn't have to change it. Most likely i had bug in handleStateChange function because i declared to many variables.

placeholder="Name"
name="name"
{...register("name", { required: true, minLength: 5 })}
/>

Below 'register' argument i added onChange and value. Everythinhg is working now.

Thanks!
Bartosz

Thread Thread
 
jlong4223 profile image
Jared Long

Nice work, thats great to hear!

Collapse
 
harrydarwin profile image
Harry Guloien

Hi Jared - great article! I am however at the end and getting an error. I was able to send mail without the front end but now it doesnt seem to work - image of the error is attached! Let me know if you need more info to help!

thanks

Collapse
 
jlong4223 profile image
Jared Long

Hey! I actually don’t see a pic attached. I see a message that says ‘error’

Collapse
 
harrydarwin profile image
Harry Guloien

error

Collapse
 
dj_d2d609399a691e profile image
DJ • Edited

Thanks for this awesome guide.

One question:
The from field in the emails that I am receiving is the address that I used to set up the OAuth. Are your from fields in the received emails the actual addresses instead?

Nodemailer documentation (nodemailer.com/usage/using-gmail/) says, "Gmail also always sets authenticated username as the From: email address. So if you authenticate as foo@example.com and set bar@example.com as the from: address, then Gmail reverts this and replaces the sender with the authenticated user." so I was wondering if it is possible to have the actual from address instead of the authenticated one.

Collapse
 
jlong4223 profile image
Jared Long

Hey DJ! Unfortunately, I haven't figured it out with Gmail and based on the docs, I don't know if it actually is possible, which is why I did a work around with adding the user's email to the subject 😅

Collapse
 
ankurssj profile image
Ankur-ssj

hey how can we add attachments(pdf specifically ) can you guide me in that?

Collapse
 
jlong4223 profile image
Jared Long • Edited

@ankurssj Hey not sure if you've figured this out or not but I have figured out the backend portion. I'm sure to wire up using attachments with the frontend will require multer to handle file requests and potentially something like Cloudinary for storing & creating urls for the attachments.

For now, here is the backend with images/files saved directly within the server project.

Essentially, you need to create an array of attachment objects (here I named mine attachmentItems) and then create an attachment key within the mailOptions object and its value is the returned result of mapping the attachmentItems.

upload-nodemailer

Collapse
 
jlong4223 profile image
Jared Long

Hey, great question and thanks for asking. Actually, I haven't done this but I really want to figure it out now! I'll look into it to expand my article and/or make a part two.

Collapse
 
anasfirly20 profile image
Anas Firly

Let's say i want to deploy it to gh pages, does the server have to keep running in order for me to receive emails from the react app form?

Collapse
 
jlong4223 profile image
Jared Long • Edited

Yes and no! If you deploy your api somewhere, it wont have to keep running since its an endpoint that will be live wherever it's deployed but if you keep the api local, it will need to keep running.

Collapse
 
anasfirly20 profile image
Anas Firly

Thanks for the reply!

Thread Thread
 
anasfirly20 profile image
Anas Firly

Do you know where I can deploy for free for backend?

Thread Thread
 
jlong4223 profile image
Jared Long

ooof good question 😅 im sure there are a ton of resources out there. I used to use heroku but their free plan doesn't exist. I haven't deployed a backend api since heroku stopped being free. I have used AWS but depending on your needs, could be complex and/or have a cost at some point. I found this blog.logrocket.com/3-free-heroku-a... . I haven't tested it but the author has some great work and tutorials that I've seen before. Could be worth checking out.

Thread Thread
 
jlong4223 profile image
Jared Long

Email.js is also helpful if you want to keep everything client side

Thread Thread
 
anasfirly20 profile image
Anas Firly

Really appreciate the replies! 🙏

Collapse
 
shifi profile image
Shifa Ur Rehman

Goldmine of an article!

Collapse
 
kiquino profile image
kiquino

This was really well explained and incredibly helpful for my self learning journey. Thank you a lot

Collapse
 
yandev profile image
Yan

thanks a lot for your tuto, it's cristal clear !

Collapse
 
godswillcode profile image
Godswillcode

It didnt work for me

it saying Error Error: self signed certificate in certificate chain

Collapse
 
senoulynn profile image
Senou Lynn

Hey! Good news! You've done everything right. What's stopping this from working is most likely your anti-virus. While I don't fully understand CORS, it has something to do with your anti-virus flagging that type of communication on your local server. I was able to bypass this by going into my Avast anti-virus => Settings => Protection => Core Shields => Mail Shield => Unclick scan outbound emails (SMTP).

Please do some research before doing this because I'm not 100% certain as to how this affects your machines security.

Collapse
 
godswillcode profile image
Godswillcode

Ok thanks bro, its now working

Collapse
 
jlong4223 profile image
Jared Long

Interesting - I haven't seen this error! How far are you able to get before you run into this error?

Collapse
 
steve-lebleu profile image
Steve Lebleu

Thanks for sharing ! Here a package which can help with email sending in node.js environment. One library for many providers.

GitHub logo konfer-be / cliam

Agnostic transactional email sending in Node.js environment

Cliam

Build Status Coverage Status CodeFactor Grade Requires.io (branch) GPL Licence

Transactional emails with a kick

Agnostic transactional email sending in Node.js environment 💥 💪 💊

> Why ?

To improve and facilitate the implementation, flexibility and maintenance of transactional emailing tasks.

> Features

  • Agnostic transactional email sending using web API or SMTP server. One input, one output.
  • Configuration based, not implementation based : easy switch between different modes.
  • Normalized transactions events.
  • Securized payloads.
  • Customisable default templates.

> Table of contents

> Requirements

  • Node.js >= 14.16.0
  • NPM >= 6.14.11

> Getting started

Install

> npm i cliam --save
Enter fullscreen mode Exit fullscreen mode

Configure

Create a .cliamrc.json file on the root of your project.

> touch .cliamrc.json
Enter fullscreen mode Exit fullscreen mode

Define a minimalist configuration in .cliamrc.json newly created:

{
  "consumer": {
    "domain": "https://www.john-doe.com"
  }
  "mode": {
    "api": {
      "name"
Enter fullscreen mode Exit fullscreen mode
Collapse
 
asalduur profile image
asalduur

this was great help, thank you!

Collapse
 
grom48 profile image
Viktor

Is it possible that you add source code of this project. thanks

Collapse
 
ayadambri profile image
Aya Dambri • Edited

Hi please help me to solve this problem
POST localhost:3001/send net::ERR_CONNECTION_REFUSED

and also this cmd doesn't work :
curl -d -url localhost:3001/send

Collapse
 
mohamedbehhar profile image
Mohamed BEHHAR

hello, does anyone succeeded to host a similar project on netlify!!