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 Node.js server to your GMail account by using OAuth2 in addition to creating a React.js Form to send an email.
Steps:
- Set up the Node.js Server
- Set up Nodemailer part 1
- Configure OAuth2
- Set up Nodemailer part 2
- Set up Nodemailer part 3
- Set up React.js
- Set up Nodemailer part 4
- Finish React
To successfully go through this article, you need to have node and npm installed on your device, a code editor, as well as a basic understanding of Node.js and React.js. This guide does go back and forth between the backend, OAuth2, and frontend, so bare with me!
To check for node and npm versions on your system, run the following commands to retrieve the version numbers:
node -v
v15.9.0
npm -v
7.5.3
Setting up the Node.js server
If you have node and npm installed, let’s get started.
Create a directory for your project. For this we will be using nodemailerAPI.
mkdir nodemailerAPI
Go into your new nodemailerAPI
directory and create a server.js
file, which will serve as our main file.
cd nodemailerAPI
touch server.js
In the terminal, run the following command to initialize a package.json file:
npm init -y
Now let’s install the packages that we will need.
For this project, we will be using Express.js, Nodemailer, and Dotenv
.
npm i express nodemailer dotenv
Time for some node.js coding!
We are going to require our packages and run our server using Express.js
. Open your directory in your code editor and input the following code:
const express = require("express");
const nodemailer = require("nodemailer");
const app = express();
require("dotenv").config();
const port = 3001;
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
Once the above code is inputted, you will be able to run your server with the following command and see the console.log:
node server.js
Server is running on port: 3001
Our server is now running, which is great news! Let’s stop the server for now with control-c
and move on to our next step, setting up NodeMailer.
Setting up NodeMailer
Nodemailer requires 3 things to begin running:
- A transporter object
- A mailOptions Object
- A sendMail method
Step 1: Lets setup the transporter object:
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: process.env.EMAIL,
pass: process.env.WORD,
clientId: process.env.OAUTH_CLIENTID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
refreshToken: process.env.OAUTH_REFRESH_TOKEN,
},
});
Important Note: The user and pass keys are your own email and your email password. We will be getting the clientId, clientSecret, and refreshToken as soon as we set up OAuth2.
As you can see, storing these variables in a .env
file are incredibly important to your privacy, so lets create a .env
file to store our auth values:
touch .env
Within the .env
file, input the following:
EMAIL=youremail@gmail.com
WORD=youremailpassword
OAUTH_CLIENTID=
OAUTH_CLIENT_SECRET=
OAUTH_REFRESH_TOKEN=
Before moving on to step two in the NodeMailer process, let's set up OAuth2 and get those other values!
Configuring OAuth2
Click the following link to go to your Google Cloud Platform dashboard. Near the top left, you will see a dropdown arrow. Once you click it, a project info modal will come up.
On the popup modal, select the new project option.
After clicking the new project button, you will see a new screen with a form to name your new project. For this project, we will be using nodemailerAPI
and you can skip the location input box. Click create.
After you click create, it will generate your new project and you will be redirected back to your dashboard.
On the same drop down(top left) as where you went to create the new project, you will be able to see your new project, which you can now select.
Once selected, open the top-left nav menu and select the following:
Once you click the OAuth consent screen, you will be brought to the following page, where you will click external:
After clicking create, you will be taken to a new page where you are required to fill in information regarding your newly created project:
Once you have filled in the above information and click save & continue, you will see the Scopes phase of the OAuth configuration. This page can be skipped so click save and continue here.
The next page is where you will add yourself as the test user:
Add yourself as a test user using your gmail and then click save and continue. The next page will be a summary of all of the inputted information, which is the end of that phase.
Next, click credentials on the left hand side, then click create credentials and select OAuth client ID:
You will be redirected to the following page, where you create an OAuth clientID:
Change the application type to Web Application. We will be using OAuth2 Playground https://developers.google.com/oauthplayground as the Authorized redirect URI:
Once those fields are inputted, you can click create.
Next, you will be presented with your OAuth clientID and Client Secret:
Copy both of those values and return to your .env
file to fill in those empty keys.
Now you should only be missing one key-value: OAUTH_REFRESH_TOKEN
, so let's go get it.
Head to the OAuth Playground. Once there, click the gear on the upper right hand side of the screen to bring up the configuration. Click Use your own OAuth credentials
and input the Client ID and Secret that you just inputted into your .env
file.
On the left hand side of the screen, you will see a large list of API’s. Select Gmail API v1 and the first option under the dropdown:
Click authorize API’s on the bottom right. This will bring you to a popup to select your gmail account. Choose your account and select continue on the next page.
Then, a new popup will come up and ask for you to grant your app permission to send email from your gmail. Click allow here.
After you click allow, you will be redirected back to the OAuth playground dashboard. Click exchange authorization code for tokens
to receive your refresh token for your .env
file:
Our OAuth2 configuration is done, so lets head back to node.
NodeMailer Step 2:
By now, you should have all of your key-value pairs filled out within your .env
file. Let's verify the transporter underneath the created transporter from Nodemailer step 1.
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
Within the terminal, run the server again and check for the console.log:
node server.js
Server is running on port: 3001
=== Server is ready to take messages: true ===
We get true! Very exciting
Underneath the verification, lets create a test mailOptions
object:
let mailOptions = {
from: "test@gmail.com",
to: process.env.EMAIL,
subject: "Nodemailer API",
text: "Hi from your nodemailer API",
};
Nodemailer Step 3:
Next, lets send the mailOptions
through a transporter sendMail
method:
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
}
});
Now, lets run the server again and within the terminal, you will see:
node server.js
Server is running on port: 3001
=== Server is ready to take messages: true ===
Email sent successfully
Check your email as it will be there!
Checkout the full server.js code at this point:
const express = require("express");
const nodemailer = require("nodemailer");
const app = express();
require("dotenv").config();
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: process.env.EMAIL,
pass: process.env.WORD,
clientId: process.env.OAUTH_CLIENTID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
refreshToken: process.env.OAUTH_REFRESH_TOKEN,
},
});
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
let mailOptions = {
from: "test@gmail.com",
to: process.env.EMAIL,
subject: "Nodemailer API",
text: "Hi from your nodemailer API",
};
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
}
});
const port = 3001;
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
As of now, there is no way to access nodemailer from the frontend as we do not have a route established. So let’s create that route.
Our transporter.sendMail
is already set up so this will be fast! All we are doing is taking the transporter.sendMail
and placing it inside of a function that is attached to a route. We have also attached a response action to send a status back, which will help determine the success in a later cURL test.
app.post("/send", function (req, res) {
let mailOptions = {
from: "test@gmail.com",
to: process.env.EMAIL,
subject: "Nodemailer API",
text: "Hi from your nodemailer API",
};
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
res.json({ status: "Email sent" });
}
});
});
Let’s test this new route using cURL.
With your nodemailerAPI
server running, open up your terminal, and run the following command in a new terminal tab:
curl -d -url http://localhost:3001/send
After running the cURL command, you will see our response come back in the terminal:
{"status":"Email sent"}
You should see an email in your inbox; Our route is now ready to use on the front-end(kindof)!
======================================================
Setup React.js
React Setup Step 1:
Lets head back to your terminal to create the new react project, which we will call nodemailer-form
by running the following command:
npx create-react-app nodemailer-form
Open this new react directory in your code editor and within src/App.js
, you will see the default react code that comes with create-react-app. Let’s remove all of it except for the top level div
:
For the purpose of this guide, we will only be coding within App.js
, which is now blank and ready for us to get to work.
Import & setup the useState Hook
At the top line of App.js
, import useState like so:
import { useState } from "react";
Our nodemailerAPI
will be looking for a name, email, and message when sending an email. So, let’s set up our useState
to reflect that:
const [mailerState, setMailerState] = useState({
name: "",
email: "",
message: "",
});
Now let's set up a function to handle the change when we type into our future input boxes.
function handleStateChange(e) {
setMailerState((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
}
Next, let’s create a form and attach the handleStateChange
to each input box as well as the name and value.
return (
<div className="App">
<form>
<fieldset>
<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
placeholder="Message"
onChange={handleStateChange}
name="message"
value={mailerState.message}
/>
<button>Send Message</button>
</fieldset>
</form>
</div>
);
If you run your react project with npm start
and view your project on localhost:3000
, it is going to be one of the ugliest forms you’ve ever seen. Let’s do a quick fix and add some inline styles to make it sort of look like a contact form:
<div className="App">
<form
style={{
display: "flex",
height: "100vh",
justifyContent: "center",
alignItems: "center",
}}
>
<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>
At this point, your form should look like this:
It is still not going to win any style contest(maybe in the 90’s), but at least it looks the part!
It doesn't work yet so we need to get it to act the part too!
We need to create a function that actually posts to the nodemailerAPI
route and attach it to the form:
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(() => {
setMailerState({
email: "",
name: "",
message: "",
});
});
};
<form
style={{
display: "flex",
height: "100vh",
justifyContent: "center",
alignItems: "center",
}}
onSubmit={submitEmail}
>
Now, our form should be good to go, or is it? If you have tried to submit the form, nothing is going to happen. If you are using Google Chrome and open up your console, you will see the following:
Our mailerState
is logging as expected but we are getting a CORs error
.
Remember earlier, I mentioned that our backend API route is kind-of ready? We have come to a point where we need to return to our API and fix a couple things.
Nodemailer part 4
Let’s go back to our node server and install our last dependency: CORs
. Within the nodemailer API directory, run the following command:
npm i cors
Now, require cors
at the top of your server.js
file and tell the app to use cors as middleware. The top of your server.js file
should look like this:
const express = require("express");
const nodemailer = require("nodemailer");
const app = express();
const cors = require("cors");
require("dotenv").config();
// middleware
app.use(express.json());
app.use(cors());
While we are on the backend, we need to change the mail options
to reflect the request from our React side.
Our current mail options looks like this:
let mailOptions = {
from: "test@gmail.com",
to: process.env.EMAIL,
subject: "Nodemailer API",
text: "Hi from your nodemailer API",
};
But this does us no good as this is what is sent every time this route is hit, so we need to change it to look like this:
let mailOptions = {
from: `${req.body.mailerState.email}`,
to: process.env.EMAIL,
subject: `Message from: ${req.body.mailerState.email}`,
text: `${req.body.mailerState.message}`,
};
Lets also update the transporter.sendMail
to send json back to react so we can alert the user of the email being sent:
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
res.json({
status: "fail",
});
} else {
console.log("== Message Sent ==");
res.json({
status: "success",
});
}
});
So, our final server.js
file should look like this:
const express = require("express");
const nodemailer = require("nodemailer");
const app = express();
const cors = require("cors");
require("dotenv").config();
// middleware
app.use(express.json());
app.use(cors());
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: process.env.EMAIL,
pass: process.env.WORD,
clientId: process.env.OAUTH_CLIENTID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
refreshToken: process.env.OAUTH_REFRESH_TOKEN,
},
});
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
app.post("/send", function (req, res) {
let mailOptions = {
from: `${req.body.mailerState.email}`,
to: process.env.EMAIL,
subject: `Message from: ${req.body.mailerState.email}`,
text: `${req.body.mailerState.message}`,
};
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
res.json({
status: "fail",
});
} else {
console.log("== Message Sent ==");
res.json({
status: "success",
});
}
});
});
const port = 3001;
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
Our nodemailerAPI
part of the guide is complete. Lets tackle the last little bit of our nodemailer-form
in React.
Back to our form in React
We just need to update the submitEmail
function to wait for the response. It is waiting for the backend to tell it if the status is a fail or if the status is a success and will give an alert accordingly.
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") {
alert("Message Sent");
} else if (resData.status === "fail") {
alert("Message failed to send");
}
})
.then(() => {
setMailerState({
email: "",
name: "",
message: "",
});
});
};
So when you try to send an email, you will get the following alert and you will see the status sent from the backend in the console:
By this point, you have successfully implemented Nodemailer with OAuth2, React.js, Node.js, and Express.js. Now you can enjoy adding this to your future projects!
Top comments (71)
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?
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:If you throw a
console.log
of the data in youronSubmit
, you should see the file info there. I'll follow up later on the nodemailer side!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.
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 ofreact-hook-form
with sending files.I actually got this figured out LATE last night. It was NOT a simple solution and was very picky.
That’s awesome! You mind sharing? Really curious to see what you came up with
This works with multi-file attachments too.
My handleChange()
So, in my API calling method I did:
and on the backend:
Nice! Solid work. Glad you figured it out and thanks for sharing
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.
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.
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.
Thanks @chris and @jlong4223 for sharing this information.
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
orhandleSubmit
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 withFormData()
: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 😅
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
What do I need to do to go past this level?
Hmm I think you could try adding the
http://
to the beginning of the url:curl -d -url http://localhost:3001/send
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...
Okay. I will try this instead and see how it turns out. Thanks!
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!
Glad that it helped :) this will work on localhost or if you deploy your api and hit the deployed url. Either way!
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.
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.
I see, alright then. Will try that out! Thank you for your tutorial and comment once again.
Thanks!! This works like a dime!
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
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 :)
Thanks Jared I was able to deploy 🙌🏽
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...
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!
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.
Hey happy to try and help. What does your code look like?
**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>
};
})
);
export default testRouter;**
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):
Thank you so much Jared, it's work. Great tutorial.
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,
Yeah for sure. Here is the entire App.js file:
Is it an error when trying to run/start your client side? Or is it an error when trying to send an email?
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!
@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 asreact-hook-form
will handle that for you & you also wont need theuseState
hook involved.Here's a few things you'll need to try:
mailerState
being sent and update the parameter name to anything else (named minedata
in this example) &e.preventDefault
asreact-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 docsHi 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
Nice work, thats great to hear!
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
Hey! I actually don’t see a pic attached. I see a message that says ‘error’
error
This is a good walkthrough thanks for sharing.
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?
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.
Thanks for the reply!
Do you know where I can deploy for free for backend?
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.
Email.js is also helpful if you want to keep everything client side
Really appreciate the replies! 🙏
hey how can we add attachments(pdf specifically ) can you guide me in that?
@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 anattachment
key within themailOptions
object and its value is the returned result of mapping theattachmentItems
.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.
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:
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"
]
}
}
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:
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!
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.
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 😅