Applying for a new job with React and NodeJS and AI
Tags: javascript, tutorial, react, node
Published: 2023-03-06 13:13:35

# TL;DR
In the previous article in the series, I walked you through how to build a resume builder application that accepts some specific information from the user and creates a printable resume.

![previously](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhfchqvcwmup6dct9mtr.gif)

In this article, we'll take the application one step further by adding a cold emailing feature that allows users to send job applications containing a winning cover letter and a resume using the [OpenAI API](https://openai.com/) and [EmailJS](https://www.emailjs.com/).

## Why do you need it?
I have been a programmer for the last decade,
but when it comes to showing my skills on paper (marketing), 
I am not the person for it.

I got filtered before in my life for many jobs just because of my resume (I haven't even gotten into the interview. We are going to change that today with GPT3 🙌🏻

![Job Things](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw30tlurabe3knct75a2.gif)

## Project Setup and Installation

Clone the  GitHub repository for the project [here](https://github.com/novuhq/blog/tree/main/resume-builder-with-react-chatgpt-nodejs).

Run `npm install` to install the project's dependencies.

Log in or create an OpenAI account [here](https://openai.com/api).

Click `Personal` on the navigation bar and select `View API` keys from the menu bar to create a new secret key.

![Add Key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk68sfdd1gj3v2sm4b59.png)

Add your OpenAI API key within the `index.js` file.

Create a component called `SendResume.js` - where users will provide the data required for sending the cold emails.

```bash
cd client/src/components
touch SendResume.js
```

Render the `SendResume` component via its own route with React Router.

```jsx
import React, { useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import Resume from "./components/Resume";
//👇🏻 imports the component
import SendResume from "./components/SendResume";

const App = () => {
	const [result, setResult] = useState({});

	return (
		<div>
			<BrowserRouter>
				<Routes>
					<Route path='/' element={<Home setResult={setResult} />} />
					<Route path='/resume' element={<Resume result={result} />} />
					{/*-- displays the component --*/}
					<Route path='/send/resume' element={<SendResume />} />
				</Routes>
			</BrowserRouter>
		</div>
	);
};

export default App;
```

Update the `Home.js` component to render a link that navigates the user to the `SendResume` component at the top of the page.

```jsx
const Home = () => {
	//...other code statements
	return (
		<>
			<div className='buttonGroup'>
				<button onClick={handlePrint}>Print Resume</button>
				<Link to='/send/resume' className='sendEmail'>
					Send via Email
				</Link>
			</div>
			{/*--- other UI elements ---*/}
		</>
	);
};
```


![Code Snippet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h683dgqbd1oja2v44mne.png)


Add the code snippet below into the `src/index.css` file.

```css
.buttonGroup {
	padding: 20px;
	width: 60%;
	margin: 0 auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	position: sticky;
	top: 0;
	background-color: #fff;
}
.sendEmail {
	background-color: #f99417;
	padding: 20px;
	text-decoration: none;
	border-radius: 3px;
}
.resume__title {
	margin-bottom: 30px;
}
.companyDescription {
	padding: 15px;
	border: 1px solid #e8e2e2;
	border-radius: 3px;
	margin-bottom: 15px;
}
.sendEmailBtn {
	width: 200px;
}
.nestedItem {
	display: flex;
	flex-direction: column;
	width: 50%;
}
.nestedItem > input {
	width: 95%;
}
```

## Building the application user interface

Update the `SendResume` component to display the required form fields as done below.

```jsx
import React, { useState } from "react";

const SendResume = () => {
	const [companyName, setCompanyName] = useState("");
	const [jobTitle, setJobTitle] = useState("");
	const [companyDescription, setCompanyDescription] = useState("");
	const [recruiterName, setRecruiterName] = useState("");
	const [recruiterEmail, setRecruiterEmail] = useState("");
	const [myEmail, setMyEmail] = useState("");
	const [resume, setResume] = useState(null);

	const handleFormSubmit = (e) => {
		e.preventDefault();
		console.log("Submit button clicked!");
	};

	return (
		<div className='app'>
			<h1 className='resume__title'>Send an email</h1>
			<form onSubmit={handleFormSubmit} encType='multipart/form-data'>
				<div className='nestedContainer'>
					<div className='nestedItem'>
						<label htmlFor='recruiterName'>Recruiter's Name</label>
						<input
							type='text'
							value={recruiterName}
							required
							onChange={(e) => setRecruiterName(e.target.value)}
							id='recruiterName'
							className='recruiterName'
						/>
					</div>
					<div className='nestedItem'>
						<label htmlFor='recruiterEmail'>Recruiter's Email Address</label>
						<input
							type='email'
							value={recruiterEmail}
							required
							onChange={(e) => setRecruiterEmail(e.target.value)}
							id='recruiterEmail'
							className='recruiterEmail'
						/>
					</div>
				</div>
				<div className='nestedContainer'>
					<div className='nestedItem'>
						<label htmlFor='myEmail'>Your Email Address </label>
						<input
							type='email'
							value={myEmail}
							required
							onChange={(e) => setMyEmail(e.target.value)}
							id='myEmail'
							className='myEmail'
						/>
					</div>
					<div className='nestedItem'>
						<label htmlFor='jobTitle'>Position Applying For</label>
						<input
							type='text'
							value={jobTitle}
							required
							onChange={(e) => setJobTitle(e.target.value)}
							id='jobTitle'
							className='jobTitle'
						/>
					</div>
				</div>

				<label htmlFor='companyName'>Company Name</label>
				<input
					type='text'
					value={companyName}
					required
					onChange={(e) => setCompanyName(e.target.value)}
					id='companyName'
					className='companyName'
				/>
				<label htmlFor='companyDescription'>Company Description</label>
				<textarea
					rows={5}
					className='companyDescription'
					required
					value={companyDescription}
					onChange={(e) => setCompanyDescription(e.target.value)}
				/>
				<label htmlFor='resume'>Upload Resume</label>
				<input
					type='file'
					accept='.pdf, .doc, .docx'
					required
					id='resume'
					className='resume'
					onChange={(e) => setResume(e.target.files[0])}
				/>
				<button className='sendEmailBtn'>SEND EMAIL</button>
			</form>
		</div>
	);
};

export default SendResume;
```

The code snippet accepts the company's name and description, job title, recruiter's name, the user's email, and resume. It only accepts resumes that are in PDF or Word format.
All the data are necessary for creating an excellent and well-tailored cover letter. In the upcoming sections, I'll guide you through generating the cover letter and emailing them.


![Import](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djzjxww13ik4xd8axr2a.png)


Import the `Loading` component and the React Router's `useNavigate` hook into the `SendResume.js` file.

```jsx
import Loading from "./Loading";
import { useNavigate } from "react-router-dom";

const SendResume = () => {
	const [loading, setLoading] = useState(false);
	const navigate = useNavigate();

	if (loading) {
		return <Loading />;
	}
	//...other code statements
};
```

The code snippet above displays the Loading page when the POST request is still pending. Stay with me as I walk you through the logic.

Update the `handleFormSubmit` function to send all the form inputs to the Node.js server.

```jsx
const handleFormSubmit = (e) => {
	e.preventDefault();
//👇🏻 form object
	const formData = new FormData();
	formData.append("resume", resume, resume.name);
	formData.append("companyName", companyName);
	formData.append("companyDescription", companyDescription);
	formData.append("jobTitle", jobTitle);
	formData.append("recruiterEmail", recruiterEmail);
	formData.append("recruiterName", recruiterName);
	formData.append("myEmail", myEmail);
//👇🏻 imported function
	sendResume(formData, setLoading, navigate);

//👇🏻 states update
	setMyEmail("");
	setRecruiterEmail("");
	setRecruiterName("");
	setJobTitle("");
	setCompanyName("");
	setCompanyDescription("");
	setResume(null);
};
```

- From the code snippet above,
    - I added all the form inputs - file and texts into a [JavaScript FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects) object.
    - The `sendResume` function accepts the form data, the `setLoading`, and the `navigate` variable as parameters. Next, let's create the function.

Create a `utils` folder containing a `util.js` file within the `client/src` folder.

```bash
cd client/src
mkdir utils
cd utils
touch util.js
```

Create the `sendResume` function within the `util.js` file as done below. We'll configure the POST request within the `sendResume` function.

```jsx
const sendResume = (formData, setLoading, navigate) => {};
export default sendResume;
```

Import [Axios](https://axios-http.com/docs/intro) (already installed) and send the form data to the Node.js server as done below.

```jsx
import axios from "axios";

const sendResume = (formData, setLoading, navigate) => {
	setLoading(true);

	axios
		.post("http://localhost:4000/resume/send", formData, {})
		.then((res) => {
			console.log("Response", res);
		})
		.catch((err) => console.error(err));
};
```

The function sets the loading state to true, displays the Loading component when the request is pending, then makes a POST request to the endpoint on the server.

Create the endpoint on the server as done below.

```jsx
app.post("/resume/send", upload.single("resume"), async (req, res) => {
	const {
		recruiterName,
		jobTitle,
		myEmail,
		recruiterEmail,
		companyName,
		companyDescription,
	} = req.body;

	//👇🏻 log the contents
	console.log({
		recruiterName,
		jobTitle,
		myEmail,
		recruiterEmail,
		companyName,
		companyDescription,
		resume: `http://localhost:4000/uploads/${req.file.filename}`,
	});
});
```

The code snippet above accepts the data from the front end and uploads the resume to the server via [Multer](https://www.npmjs.com/package/multer). The `resume` property from the object contains the resume's URL on the server.

## Generating the cover letter via the OpenAI API

In the previous article, we accepted the user's work experience, name, and list of proficient technologies. Make these variables global to enable the `/resume/send` endpoint to access its contents.

```jsx
let workArray = [];
let applicantName = "";
let technologies = "";

app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
	const {
		fullName,
		currentPosition,
		currentLength,
		currentTechnologies,
		workHistory,
	} = req.body;

	workArray = JSON.parse(workHistory);
	applicantName = fullName;
	technologies = currentTechnologies;

	//other code statements...
});
```

From the code snippet, the `workArray`, `applicantName`, and `technologies` are the global variables containing the user's work experience, full name, and skills. These variables are required when creating the cover letter.

Update the `/resume/send` endpoint as done below:

```jsx
app.post("/resume/send", upload.single("resume"), async (req, res) => {
	const {
		recruiterName,
		jobTitle,
		myEmail,
		recruiterEmail,
		companyName,
		companyDescription,
	} = req.body;

	const prompt = `My name is ${applicantName}. I want to work for ${companyName}, they are ${companyDescription}
	I am applying for the job ${jobTitle}. I have been working before for: ${remainderText()}
	And I have used the technologies such as ${technologies}
	I want to cold email ${recruiterName} my resume and write why I fit for the company.
	Can you please write me the email in a friendly voice, not offical? without subject, maximum 300 words and say in the end that my CV is attached.`;

	const coverLetter = await GPTFunction(prompt);

	res.json({
		message: "Successful",
		data: {
			cover_letter: coverLetter,
			recruiter_email: recruiterEmail,
			my_email: myEmail,
			applicant_name: applicantName,
			resume: `http://localhost:4000/uploads/${req.file.filename}`,
		},
	});
});
```

- From the code snippet above,
    - We destructured all the data accepted from the React app and created a prompt for the AI using the data.
    - The prompt is then passed into the OpenAI API to generate an excellent cover letter for the user.
    - All the necessary data required for sending the email are then sent as a response back to the React app.
    - The resume variable holds the document's (resume) URL.

Recall that from the previous tutorial, the `ChatGPTFunction` accepts a prompt and generates an answer or response to the request.

```jsx
const GPTFunction = async (text) => {
	const response = await openai.createCompletion({
		model: "text-davinci-003",
		prompt: text,
		temperature: 0.6,
		max_tokens: 350,
		top_p: 1,
		frequency_penalty: 1,
		presence_penalty: 1,
	});
	return response.data.choices[0].text;
};
```

## Sending emails via EmailJS in React

Here, I'll guide you through adding EmailJS to the React.js application and how to send the AI-generated email to recruiters.

Install EmailJS to the React application by running the code below:

```jsx
npm install @emailjs/browser
```

Create an EmailJS account [here](https://dashboard.emailjs.com/sign-up) and add an email service provider to your account.

Create an email template as done in the image below:

![Curly Brackets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tlhrvkwsngyc8agr2eb.png)

The words in curly brackets represent variables that can hold dynamic data.

Import the EmailJS package into the `util.js` file and send the email to the recruiter's email.

```jsx
import emailjs from "@emailjs/browser";

axios
	.post("http://localhost:4000/resume/send", formData, {})
	.then((res) => {
		if (res.data.message) {
			const {
				cover_letter,
				recruiter_email,
				my_email,
				applicant_name,
				resume,
			} = res.data.data;
			emailjs
				.send(
					"<SERVICE_ID>",
					"<TEMPLATE_ID>",
					{
						cover_letter,
						applicant_name,
						recruiter_email,
						my_email,
						resume,
					},
					"<YOUR_PUBLIC KEY>"
				)
				.then((res) => {
					if (res.status === 200) {
						setLoading(false);
						alert("Message sent!");
						navigate("/");
					}
				})
				.catch((err) => console.error(err));
		}
	})
	.catch((err) => console.error(err));
```

The code snippet checks if the request was successful; if so, it de-structures the data from the response and sends an email containing the cover letter and resume to the recruiter.

The variables created within the template on your EmailJS dashboard are replaced with the data passed into the `send` function. The user is then redirected to the home page and notified that the email has been sent.

Congratulations! You've completed the project for this tutorial.

Here is a sample of the email delivered:

![Conclusion](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe8q5s1w8z0i2i6j3tnz.png)


## Conclusion

So far in this series, you've learnt:

- what OpenAI GPT-3 is,
- how to upload images via forms in a Node.js and React.js application,
- how to interact with the OpenAI GPT-3 API,
- how to print React web pages via the React-to-print library, and
- how to send emails via EmailJS in React.

This tutorial walks you through an example of an application you can build using the OpenAI API. A little upgrade you can add to the application is authenticating users and sending the resume as attachments instead of URLs.

> PS: Sending attachments via EmailJS is not free. You'll have to subscribe to a payment plan.
> 

The source code for this tutorial is available here:

[https://github.com/novuhq/blog/tree/main/cold-emailing-with-react-ai](https://github.com/novuhq/blog/tree/main/cold-emailing-with-react-ai)

Thank you for reading!

## A small request 🥺
We are Novu - an [open-source notification infrastructure](https://novu.co) service that can manage all your notifications channels (Email / SMS / Push Notifications / Chat / In-App).
You can use Novu to automate all of your channels together in a simple dashboard.

I produce content weekly, your support helps a lot to create more content.
Please support me by starring our GitHub library.
Thank you very very much! ❤️❤️❤️

https://github.com/novuhq/novu

![Help](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7786qoi7ucrfper5sn83.gif)