Applying for a new job with React and NodeJS and AI
Nevo David
Posted on March 6, 2023
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.
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 and EmailJS.
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 🙌🏻
Project Setup and Installation
Clone the GitHub repository for the project here.
Run npm install
to install the project's dependencies.
Log in or create an OpenAI account here.
Click Personal
on the navigation bar and select View API
keys from the menu bar to create a new secret key.
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.
cd client/src/components
touch SendResume.js
Render the SendResume
component via its own route with React Router.
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.
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 ---*/}
</>
);
};
Add the code snippet below into the src/index.css
file.
.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.
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 the Loading
component and the React Router's useNavigate
hook into the SendResume.js
file.
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.
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 object.
- The
sendResume
function accepts the form data, thesetLoading
, and thenavigate
variable as parameters. Next, let's create the function.
Create a utils
folder containing a util.js
file within the client/src
folder.
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.
const sendResume = (formData, setLoading, navigate) => {};
export default sendResume;
Import Axios (already installed) and send the form data to the Node.js server as done below.
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.
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. 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.
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:
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.
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:
npm install @emailjs/browser
Create an EmailJS account here and add an email service provider to your account.
Create an email template as done in the image below:
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.
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
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
Thank you for reading!
A small request 🥺
We are Novu - an open-source notification infrastructure 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! ❤️❤️❤️
Posted on March 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
March 12, 2022