Sebastian Pieczynski's website and blog
Published on: yyyy.mm.dd
Published on: yyyy.mm.dd
Created with ❤️ by Sebastian Pieczyński © 2023-2024.
Published on: 11/9/2023
When I was building this site I wanted to give people ability to contact me directly. Social media is one way but I don't want to use third party service and sending an email is a bit more personal and direct. But how to do it without using external services?
What do we need to send emails?
There is quite a bit that goes into such a simple task but breaking it down will make it easier to complete.
We'll take it one step at a time. First let's prepare our "infrastructure".
For email account I'll be using Google Mail, but the way I'm showing should work with any email provider.
Since there can be many places where we might want to use the contact form let's create a component that will encapsulate the functionality. We'll split the form into two components: Submit
and Form
. Why will become clear later when we're using Server Actions and new React hooks to handle the form status.
Submit button and the form:
Nothing too fancy for now. We are creating three inputs for: name, email and message in the form and allowing it to be submitted with a button. Usually we would create a 'handleSubmit' function and attach it to the onSubmit
event on the form, but not today 🙂.
With our form done we can start thinking about functionality.
When sending emails using javascript, nodemailer is the first solution that comes to mind.
Let's install it and start wiring our form.
To add nodemailer to the project with npm
use the following command:
With the library prepared for use let's start adding functionality...
Before we start coding we need a way to secure our connection data (username and password). We'll use .env
file to store it.
Install dotenv
package:
Then in .gitignore
modify .env section and add the following before proceeding:
Create two files: .env
and .env.dist
First let's fill out data for .env.dist
. Note that this file is used to "save" what data is required to be set as the file with actual data will NOT be saved in the repository. Without it we would need to check what environmental variables are set in code and if we missed any it would be a pain to find.
You can make a copy of empty .env.dist
and name it .env
and fill it out.
To use gmail account we need to add password to .env
. Instead of using you password you can generate application password. This is different from less secure apps access, see: Sign in with app passwords
Here are the steps required to obtain the application password:
Create & use app passwords
Important: To create an app password, you need 2-Step Verification on your Google Account.
If you use 2-Step-Verification and get a "password incorrect" error when you sign in, you can try to use an app password.
The password is visible only once. Copy it to your .env
file.
Example:
If you want more flexibility you could add HOST and PORT variables like so as well, here's data for Gmail:
Now we are ready to start writing the function that will be sending emails.
nodemailerSendMail
functionThe nodemailerSendMail
function will accept four parameters: replyTo
, subject
, toEmail
and otpText
.
replyTo
: the email address of the sender, as by default the sender will be set to our internal email
subject
: the subject of the email
toEmail
: the email address of the recipient
otpText
: the text of the email
The section of note is:
When deploying to serverless we must wrap the sendMail
in a promise and await
for it to either resolve
or reject
then we return that result.
We can now send emails from your server. We'll now use server actions to handle the form submission.
To add server action to the form instead of using onSubmit
event handler we connect the function to the action
attribute of the form.
First let's define the formAction
and form state using useFormState
hook. This hook "allows you to update state based on the result of a form action".
Let's define initial form state. Note that I've also added error
as it's a property that can be returned from the form action.
And the action itself:
Our form will now change from:
to:
You probably noticed that we do not have sendEmailAction
defined anywhere. Let's fix that.
Go to the /utils/sendMail.ts
fire and add sendEmailAction
function:
The ('use server');
in the beginning of the function tells Next.js that this is a server action and that it should be run on the server.
Here we are verifying data sent to the server from the form making sure it's non empty and attempting to send the message using the previously defined nodemailerSendMail
function. If it sends successfully we return true otherwise we return false. To verify this we also check the response from the server, see: SMTP response codes . This will be important when we want to notify users about the message status.
You can check the form now and it will send the mail to selected inbox.
Note that for it to work you need to check how to set Environment Variables for your hosting provider as .env
files are not part of the repository and SHOULD NEVER be committed.
For notifications I prefer the toast messages as they are generally unobtrusive. We will use react-toastify
package to display them.
First let's install it:
You can see the react-toastify documentation here .
And as per document suggestion:
Remember to render the ToastContainer once in your application tree. If you can't figure out where to put it, rendering it in the application root would be the best bet.
We'll put it in the main layout as we may want send out messages from different places and about various events.
The settings you see for the ToastContainer
can also be configured using official website .
The most important that we care about is position
, we change it to "bottom-right"
. We also use toastClassName
and progressClassName
properties to style the component. Import the component's styles before global tailwind styles as they will get overwritten otherwise.
Let's add toast notifications for our users!
We will need to update the formAction
to change the status of the toast if the email was sent successfully or not.
If everything went fine we'll invoke updateMailSuccess
and if not we'll invoke updateMailError
. This is why the function was returning boolean
value as it makes it easy now to know if the email was sent of not.
To control how notifications are displayed we will use the method shown in the official documentation show here .
To create a toast it is enough to call the toast
function like so:
You are not limited to calling or using the toast
function to place where ToastContainer
is defined. It can be any other place in the app. Just remember to have only one ToastContainer
.
Our case, as it usually stands with projects I start, is a bit more complicated. Luckily for us react-toastify
package has all the building blocks and examples to fit our need exactly.
We need to render the initial toast state (sending message in progress, without a timeout) and then depending on the state of the response update the existing toast to show success (if true was returned from the email function) or error (if the response was false).
If you remember the docs or notes above the toast
function arguments supersede the ToastContainer
component. We'll use that to our advantage.
To accomplish our goal we need a reference to the toast which we will update to success or error states later.react-toastify
shows how to do this in the official documentation .
Let's start writing our toast functions. First let's start with sending initial information that sending is in progress:
It's as simple as the example with the two exceptions: first is that we are assigning the toast to the toastId
ref (see more in the React documentation ) that is the reference to the toast. This is because we want to update it later on. Second is that we set autoClose
prop to false
that way the toast message will not have a timeout and it will never disappear. Now we need functions that will update it to show success or error, these are again standalone and do not depend on the way we implement the toast.
Here we use the update
method of a toast with the reference to the toast we want to update. We set autoClose
to 5000
so that the toast message will disappear after five seconds after the update.
Now we can update the formAction
method in the useFormState
hook as well as the form itself to show the initial toast when starting to process the action:
When we receive the response from the sendEmailAction
function we update the toast message to success or error depending on the response value returned. If it was successful we also reset the form state using the form reference created with another useRef
hook.
We should also disable the send button when mail is being sent as we do not want to be spammed with the same message.
Congratulations! You now have a contact form that users can fill out and tell you how awesome your website is 😉 and all of it free!
Below is the full source of the ContactForm
component.
Today we have created a contact form for our visitors to contact us using new feature in Next.js 14, we explored how to use server actions to achieve the task and how much simpler that process is from setting up the full API route. We also learned how to send emails using nodemailer and how to use react-toastify
to display notifications to the users about the status of the process. We have also learned a bit about how to use ref
s in React and how they can be useful for forms.
As always you can contact me (via the form 👇 for example 😄) and let me know what you think. Any feedback is welcome and I will use it to improve the article.