How to Send Emails with Next JS and Nodemailer

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's going on guys welcome back to another video today we're going to be building this contact form in next.js and it's going to have some basic validation so if they don't have a field filled out the submit button will be disabled and this error state will be displayed but once they filled everything out they can hit submit and that's going to go ahead and send an email and this is what the email is going to look like it's going to have all our fields and then the values that they input and then we can email the customer with the email they put here I'm also going to be using a Gmail account to send and receive emails so if you want to follow along you want to make sure you have a Gmail account and without further Ado let's get into it all right so I have the starting files open and these are going to be linked in the description below so you guys can follow along and right now we just have this contact heading so if I open up our folder you can see in the Pages directory our index or our home page just has this heading that says contact and then in our app.js this is what next.js uses to build out every page so this component that it gets in its props represents each page component that we Define in the Pages directory so we could wrap the page component with an app container which is what I'm doing and just setting some Global Styles and rendering the children and then I'm also wrapping it with the chocolate provider to give the chakra components access to certain Styles and properties and then also this theme just sets the default color mode to dark so that's it for the starting files and now we can start building out the contact form let's begin by wrapping our entire page with a container and all of our content is going to go inside so let's move our heading up and then let's also give the container a Max width so that the form doesn't extend that so we'll do 450 pixels and then let's also give it some margin top we'll just do 12. so that should add a little bit of spacing I'll get rid of the semicolon now we can begin working on the form inputs and the way those are going to work is we're going to create a form control component and inside of that form control we're going to have a form label an input and a form error message and the form control is going to help us display the error state if the input has an error so let's go back and add all those things so we'll do form control and that comes from Chakra and then inside we're going to want a form label and the first label we want is the name and then also in this form control we want to set it to required so we'll just set is required and that'll just display a little asterisk at the top and now we want to create our input let's create that here and now let's set some properties on it so we could do the type and we want to be the type text and we also want to give it a name we're going to use that to set our state later so the name will just be name because that's the label here and now if we hit save we should be able to see that in our app so there's our first field right now this isn't being controlled by any sort of state so let's come up here and just create some state for our form and I'm just going to create an object up here called init State and right now we're just going to have a values property this property is going to store all of our form values but our state is also going to have other properties like the loading or error States but I will add those later in here I want to create another object for the specific form values so we'll do init values and then that will just contain all of our form values so I'm just going to paste those in real quick and then I'll set these values to init values and now we can use this in its state as the default State here okay so now back in our input we want the value to be set to whatever values in the state first I want to destructure all the form values from the state so that we don't have to access it by doing state.values every time so we'll just do const values we're going to get that from the state so now we can just use values dot name so in here we'll do values dot name now this input is hooked up to the state but now you'll see that we can't update the state and that's because there's no on change Handler defined uh so let's define that real quick we'll do const on change we'll do handle change this is going to be a function that's going to take in an event and then we want to update our state let's go ahead and call set state and in set State we could either pass in a new object to set that state to the object or we can use a function and the function has a parameter that is the Old State so we get the previous state and then we want to return a new object which will be our new state and in here we want to spread the Old State so we'll just spread out previous and that's because if we have other properties on the state object here we don't want to we want to retain those so we'll spread those out and then we want to override the values property and in here again we want to spread out the previous values so we'll do previous state DOT values just because when we update the name we want to retain the email and the subject and so on so then in here we can open up square brackets and on the event we have access to the input that triggered it so we'll have access to the name property on the input and also the value of it so in the event I actually want to destructure the Target which will be that input and as the property here we want the target dot name and then the value will be Target dot value so again we're setting this name property here and whatever this string value is is whatever the property name will be in the state and then we're just setting it to the input value that triggered the event so now we can just set this as our on change Handler and that component should now be controlled all right so now we can just copy and paste this for all of our other inputs so I'm going to do that real quick so the only input that'll be different is the message and instead of this being an input it's going to be a text area but all these properties can stay the same so now if we go back to our app we'll see all of these and we'll be able to type in all of them and let's just add some margin bottom to all of them so I'll just add mb5 and that should give all of them a little bit of margin okay that looks good let's also give the text area some rows and we'll set that to four and then for the email input we want to make sure that it's of type email the next thing we need to do is to show some error state in our form we can do that by a setting that is invalid property on the form control let's come in here and do is invalid and we'll just set this to true for now so it'll always be invalid and in order for our input to display in an error State we can give it the error border color and we'll just set that to red.300 this is just going to set it to the chakra red default colors and then the 300 just represents the darkness of the color so it's going to be a lighter red and then also we can display the form error text or error message that'll be from Chakra and here we'll just say required we save this and go back to the form you'll see that this input is displayed in the error state right now it's just hard coded to always be in an error state so let's change that instead of it being true we want it to be in the error State when there's no value so we could do values dot name and then we want to check if this is falsy so if values that name is false then is invalid will be true and then the input will know to use the error border color and Chalker also knows that when is invalid is true then it's going to display the form error message so if we just save that then you'll see that it's in the error State when there's no text but then when we add some text it'll go away so let's just add that for the rest of the input all right now that that's added we can see that all the fields have this validation if we go to refresh the page we can see that the form initially displays in this error State and that's because we don't have any values filled in ideally we'd want the form to display in its normal State and then once the user clicks on an input and type something and then clicks off of it that's when we want the form input to validate itself because initially this would not be a great user experience so the way we're going to do that is we're going to create another piece of state and that's going to contain all of our touched values so we'll do touched and then set touched and this will be an empty object initially then in our input we could come down here and check that the value was touched so we could do touched dot name and values.name is undefined if both of these are true then this input will be invalid so if we save this we'll see that it no longer initially displays in the error State because it hasn't been touched so we want the user to first touch the input and then when they touch out of it or when the input is Bored then we want to validate it so let's create our onboard Handler we'll call it onboard it'll be a function and again we're going to destructure the Target and then we'll call set touched and here we'll get the previous state in the function and we'll return a new object retaining that previous state and then we want to set the property that came from the target so we'll do Target dot name and for the value we'll just set it to true so then in our touch State we'll just have all of our property names then true if it has been touched so let's just add this on blur Handler to each input and if we go back to our app and refresh you'll see that they no longer display in an error State except for the ones that aren't checking if they're touched so we also need to add this check to see to verify that they've been touched so I'll just add that to every input now if we go back and refresh we'll see that they all are validated on the blur and then once you type the error goes away because it has been touched and there's a value so this is working exactly how we want it to the last thing we need for our form is the submit button so let's go ahead and add that so we'll import the button and here we'll just do submit and then let's give it some props so the variant we want it to be outline we also want the color scheme to be blue and that'll just add some blue styles to it we also want the button to be disabled when any of our form values are not defined so we want to just grab all the values and do a check so we're going to check if the values.name is undefined or the email is undefined or subject or message and if any of these are undefined then disabled will be true and then it will display the button in a disabled State and we won't be able to click on it and then the last thing we need to add is an on click Handler and we'll just call it on submit so let's go ahead and create this on submit Handler it's going to be an async function and in here we want to set our form state to loading so in order to do that we'll call set state we'll get the previous state and again return a new state object retaining the previous state except now we want to set a new property and we'll set the is loading property and we'll set that to true so now when we hit submit is voting will be true and then in our button when we click submit we want it to display in a loading state so we'll put the is loading prop and whenever this is true the button will display in that loading state so we'll just check state or actually we can just destructure it from our state object up here we want values is loading and now we can just use that down here so if we come back we'll see that the button is disabled because email Isn't filled out then when we fill it out we'll click it and the button is in a loading state let's just remove this space and then in our on submit Handler we need to post our form data to an API endpoint we're going to be using the built-in next.js API route and the way that those work is every file under the pages slash API directory will get an Associated API endpoint based on the file name so right here we have this hello.js file and this is just the default one that came with the next app so every file here will get its own endpoint so if we go to our app slash API hello it's going to trigger an API request and it'll be handled by this function and next.js will take whatever function you export as the default from each file in the API folder and it'll use that to handle the request and this function will have access to the request and response objects which are pretty much identical to the express.js objects if you ever work with that and here we could just set a response so this handware is just we're returning a 200 with this Json payload to every request to slash API hello so if we go back in our browser and we go to slash API slash hello we'll see this Json payload gets returned so for every endpoint that we want to build we need to create a file name for it in our case we want to post to the contact route so we'll just rename this to contact and now we can start building out the contact Handler before we actually create the Handler let's create a function that will post to the endpoint so let's create a lib directory and in here we'll just create a file called api.js and this will contain all of our API requests we'll export a function called send contact form it'll be async and it's going to take in some data and then we're going to call the fetch API and we want the endpoint to be slash API slash contact because that's what we defined as our file name here for our Handler and then as the second parameter we're going to pass in an options object and we want to set the method we'll set it to post we want to set the body of the request and we're going to stringify the data that was passed in so we'll call it json.stringify and pass in that data and then we also want to set the headers and this is important because when next.js in the request handlers sees that the header content type is application slash Json it'll know to automatically parse the body for you so we won't have to do that on every request so I'm just going to paste in these headers and it just basically says that the content type we're sending is Json and we accept Json as the response so now that we have this we can go back to our contact form and call this function let's import it and then we want to pass in the values and remember these values come from our state which should have all of our form values so if we hit save and we go back to our Handler we can log out the request body and we should be able to see all of our form values let's go back to our form and just input some dummy text and then hit submit and then we'll see in our console here we'll see this request body and that was logged in this Handler because we made the request to slash API contact came to the Handler and we have access to the request here and then we sent back this response so now let's build out the Handler first I want to convert this to an arrow function and in here as I mentioned before we have access to the request in our case we just want to handle post requests to the slash contact route so let's change this response to be the default error Handler so we could send back a 400 and then we'll also just return a message and we'll just say bad request so this will serve as our catch-all error Handler we could just return this so now in here we can check the method of the request and verify that it was a post request so we'll do if request dot method triple equals post then we want to handle it otherwise it won't make it in this if block and then we will eventually send back this 400 response once we verified that it was a post request we want to grab the request body so we could do const data equals request.body and that'll grab the Json payload we send and then we want to run some validation we can check if any of the properties that we're expecting are defined so let's just go back to our contact form and copy all of these here so if any of these conditions are true if the name or email is undefined then we'll make it into this block and we just want to return out the same error response okay so if we get past that check then we know that all these values were defined then we know that we're able to send the contact form because all the fields are filled out the next thing we need to do is to actually send the email I'm going to be using node mailer to help us do that and if we look at their documentation you could see that all we need to do is create a transporter object by calling the create transport method and passing in some options and then we just need to call send mail on the transporter so that's pretty simple let's come back to our app and create the transporter so in our config folder we could create a file called node mailer and we're going to create our transporter here and then export it out into any API brows that we need so we just want to create it in one place as opposed to on the fly on every request to create the transporter we could do export const transporter and we're going to set it equal to node mailer dot create transport we need to import node mailer from the top and on the create transport method we could pass in an options object and on here we could Define a ton of properties but the one we want is the service and we're going to set that to Gmail because I'll be using a Gmail account but you could also put Yahoo or iCloud or whatever service you're using but if you want to follow this tutorial you're going to need to use Gmail and then we also need to pass in an auth object and this object is going to contain a user which will be the email that we're going to use and then also a password I don't want to put in the username and password just in plain text in the code so instead we're going to want to use environment variables and if you pull down the code you'll see that there's a sample EnV file so you're going to want to copy those variables and then at the root directory create a DOT env.localfile and next.js will know that when you're developing locally it'll use this env.local file to grab all the environment variables in here but once you're in production it's not going to use this file so we can come in here and just Define the variables in the sample for the email I'm just going to copy mine in and then for the password if we go to the node mailer docs you'll see that they have a page on using Gmail and it says that if you're using two-factor authentication you have to create an application specific password for node mailer to work and my Google account does have two-factor authentication so I'm going to need to create this application specific password so I'm going to go to my Google account and click manage your account and then in here I'm going to go to security and then down to app passwords so in here I'm just going to need to enter my password and then once we're here we can select an app here and I'm just going to do other and I'll call it node malware and then I'm going to generate a password the code here is the password that was generated and I'm going to delete this right after the video so it's not going to be useful but once you have that copied you just want to paste that in to your environment and once you hit save it should restart the server and load the new environment file so we could just close out of here and now we can pull in those variables and then we could use them down here in the auth object so we'll put email down here and then password can just say password I'm also going to want to export a mail options object and we're going to use that later when we call the send mail method in here I just want to define the from and to email and that's just going to tell node mailer from what email we're sending the email from and then which email we should deliver it to and in our case we're just going to use the same email for both because the way that this is going to work is we're going to be sending ourselves an email because when the user fills out a form on our website they're not giving us access to use their email and send an email on their behalf so instead we have to use our own email and just send that email to ourselves and we'll know from the contact data who actually submitted the form I hope that makes sense now that we have these options and the transporter we could go back in the API route and use it let's create a try catch block so if the transporter throws any errors we want to be able to catch those and we could walk the error to the console and then we'll return an error response except for the message instead of bad request we want to do error Dot message now in the chat block we want to await the transporter send mail method let's call The Transporter and import that at the top and then call send mail and send mail takes an option to object we want to grab these mail options and spread those in here import those as well and then we also want to set the subject and that will be equal to the data.subject whatever the person put in on the form and then we also need to add the text which is just going to be a string of the email and this is what the user will see if their client can't render HTML so we'll just say this is test string and then lastly we can set the HTML property and this could be a string of HTML that will actually be rendered in the user's browser so we could add heading tags and paragraph tags and we could make it look a bit nicer so let's just add an H1 and we'll just say test title and then I'll add a paragraph tag and we'll just say some body text and we also need to change this to an async function in order to use the weight and if this send mail method was successful we want to return a success response so if we hit save now and go back to our form we could put in some dummy data and once we hit submit we should see that email being sent and right now we aren't handling the response from the API but if you look in the email for some reason there was an error values is not defined and that's because we need to change this to be data because that's what we call the body here and now if we go back and refresh and fill out the form again it should work so if we hit submit it'll load for a second and we should see this email and there's our H1 and our paragraph tag and then you can see this is the text string that we put so in our case instead of just putting the HTML like this I want to use a template if you look at the templates directory and open up this email.html file you'll see that this is what our contact form is going to look like and right now does it have any fields or values it just has this HTML data and that's because in here this is going to be the div that holds all of our form data and I just put in this placeholder value but in our actual code we're going to replace HTML data with the actual heading and paragraph tags so this is just acting as a placeholder so back in our API route instead of setting the text at HTML like this I want to create a function that's going to return these two properties and then I'm going to spread them in so let's come to the top and we'll call the function generate email content and it's going to take in some data and that's going to be the form data and in here again we need to return those two properties so let's return an object with text and HTML and then we're going to fill these out as we build out the function so the first thing I want to do is build out the text string and in order to do that I'm going to take our data object with all the keys and values for the form that was submitted and I want to put them all into one string let's call the variable string data and it's going to be equal to object dot entries and then we're going to pass in the data the entries method will take in an object so I did a little example here with this object and it returns an array with each item in the array being another array with the key and the value for each property in the object so at the end you'll get an array of all the key and value pairs for each property that was in the object so once we have that we want to call the reduce method and this method is going to take a function as a parameter and then also a starting or initial value right here and in our case it's going to be an empty string and that's going to be our container or accumulator value so we're going to keep appending to this string by default it'll just be empty and then our reducer function gets that accumulator or whatever value we put here as the first parameter so we could do string and then the second parameter is the item in that array in data for the first item it'll be this array and then the next iteration it'll be this array and it'll keep going down until it gets to the end so we want to destructure the key and the value of each item so let's do that we'll open up the square brackets and do key value and that'll get all the values and then inside the function whatever we return from here becomes the new accumulator value which will be returned to the next iteration let's take the string and on the first round it's just going to be empty so we're going to want to append so let's add that plus sign before the equal sign so we're going to be setting string equal to whatever I add to the right of this equal sign and I want to append every single key and value let's first get the key I'm going to open up these backticks and then instead of just putting the key in like this on the object it's going to be lowercase but on the form I want it to be uppercase so I'm just going to create a quick little object map that's going to map each property in the form to the name that we want to see on the email so I'll call it contact message fields and then for the name property I want the actual email field to be named and then all of them are just going to be uppercased in here instead of outputting the key on the object I want to get the key off of this object so I'll do contact message fields and then I'm going to pass in the key here so then for email it's going to get this value as the key and then I'll do a colon and then I'm also going to Output the value and it doesn't like this for some reason and I need an a closing brace there okay I'm also going to want to add some extra space in between each key and value so that it's not all jumbled up into one sentence so I'm just going to add some returns and then after each value I want to return twice to give it some extra spacing okay so for each item we're going to get the key and the value and then we're going to take the accumulating string and we're just going to append the key that is based off of this contact message fields map so either get name email or subject and then we're going to do a colon and set it equal to the current value of the item in that array so in the end the string data is just going to be a long story of all of our keys and values so for our text we can return string data here and now let's create the HTML data and before we do that I realize we have to actually return this string because whatever this function Returns on each iteration becomes the new value for the following iteration so if I return this string with the added values that's what's going to be the string value on the next item and then it keeps going on till the end and since this is an arrow function we don't need these curly brackets because we could just remove the return and with the arrow function syntax it'll node automatically return this string we can also get rid of the semicolon and I realized we had an extra bracket here so we can remove that as well now this becomes a lot more concise let's create the method for generating the HTML so we'll do const HTML data this is going to work in a very similar way so I'm just going to copy and paste these lines and instead of returning the key and the value in a string I want to return some HTML for each key I'm going to return a heading tag so let's create that we'll do H1 and then in here I want to Output the key so we'll do the dollar sign and the curly brackets we want to get the key from this object not the original form data key and that'll be the content for the H1 and then for styling purposes I want to add a class name to it and if you look up here I defined some classes select the form heading which will just set some CSS properties and then I also have a class for the form answer and that's going to be the paragraph tag let's go ahead and add those classes to The Heading tag so we'll just say class and we'll get the form heading and then I also want to give it an align of weft now we have our H1 and then the next thing we need to add is our paragraph tag so let's open that up and then here we're going to want to just display the value and then we also want to paste in a class and the Align left except we're just going to want to change the class name to be form answer so on each iteration for each key and value pair in that data object we're going to be appending an H1 tag and a paragraph tag so at the end this string will just be a long string of H1 and paragraph tags now that we have that we want to replace this placeholder value that has HTML data with the generated string that we created here so I went ahead and I just copied the entire file and ran it through an HTML minifier and that removed all the white space so I'm just going to paste in that string here and you can see that it's all just pushed together but we still have this placeholder value since it's in that dollar sign we are injecting it in as a variable and if we go and find it you'll see that when we click on it it's equal to this string that contains all the H1 and the paragraph tags so we're basically injecting the H1 and paragraph tags into this long string that is basically this HTML file so in the end this will contain our E's and values of all the form fields that were set now that we're returning the text in HTML we need to use this function down here so we'll remove these properties and then above the subject we're just going to spread out the content or the return value of this function then we're going to pass in the data as a parameter so now the transporter is done and it should be able to send mail successfully let's go back to the front end and finish this submit Handler right now we're just calling send contact form and we're not doing anything with the response and we're also not handling any errors if something goes wrong so let's first open up this function and I want to paste in some error handling if this API request returns a 400 or 500 so I'm going to tack on a DOT then and I'll get the response and then I'm checking if response.okay is falsy and in the case of a 4 or 500 status code then response.okay will be false and in that case I want to throw the error with this error message failed to send message otherwise if it was successful I'll just return the response.json and that's basically just going to return the response body parsed so since we're throwing this error we can then go back in our submit Handler and wrap all this in a try catch so that we can catch the error that this potentially throws and then I just want to set the state to include that error that was thrown so I'll just call this set State I want to set is loading to false and then I also want to set the error property and I'll set that to the error.message and that's going to be equal to the error message we defined here because this is the error we're throwing so now we can come up here and destructure the error from our state we could do a check below the contact we could check if error is defined and if it is we're going to Output some text with this light red color and give it some margin and then we just want to display the error above the form so let's manually return an error and instead of checking if the method is post I'll just do the opposite of that so then it's always going to just return this 400 and now if we go and fill out our form we should see that error and there it is so there our error handling is working so now let's undo that and let's work on the success result on success I want to basically reset the form so I'm going to set the touch State and the form data to the initial state so the touch would just be an empty object because that's what it was initially and then I'm setting the form state to the init state which will just have values equal to this initial starting value and then lastly I just want to display a toast for when it's successful and that's that green pop-up that will come up at the top to do that I need to import used host from Chakra we'll do cons toast equals used host and then we can just invoke this toast to make it pop up so after we've set the state we'll just invoke it and we'll give it a success status so that it's green it'll display for 2 000 milliseconds it'll be at the top and this is the message we want to display so now if we hit save we should be able to fill out the form and see the email come in with that template that we used so now I'll hit submit and we'll see if it all goes through and it looks like it did so if we check our email we should see that new message and it's formatted using our template and here are all the fields that were passed and now as the user since this email came from me I could just take the email that was passed in the form and then respond to them and reach out to the customer that's it for this video thank you guys so much for watching leave a like if you enjoyed And subscribe if you want to see more content we're almost at a thousand subscribers and I just really appreciate all the support you guys have been showing so thank you for that and I'll see you guys in the next one [Music]
Info
Channel: Onelight Web Dev
Views: 32,736
Rating: undefined out of 5
Keywords: javascript, react, reactjs, nextjs, nodemailer, javascript emails, nodemailer emails, email contact form, nextjs tutorial, nextjs emails
Id: t2LvPXHLrek
Channel Id: undefined
Length: 37min 25sec (2245 seconds)
Published: Sun Nov 13 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.