Applying for a new job with React and NodeJS and AI
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 event).
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.
1cd client/src/components
2touch SendResume.js
Render the SendResume
component via its own route with React Router.
1import React, { useState } from "react";
2import { BrowserRouter, Routes, Route } from "react-router-dom";
3import Home from "./components/Home";
4import Resume from "./components/Resume";
5//👇🏻 imports the component
6import SendResume from "./components/SendResume";
7
8const App = () => {
9 const [result, setResult] = useState({});
10
11 return (
12 <div>
13 <BrowserRouter>
14 <Routes>
15 <Route path='/' element={<Home setResult={setResult} />} />
16 <Route path='/resume' element={<Resume result={result} />} />
17 {/*-- displays the component --*/}
18 <Route path='/send/resume' element={<SendResume />} />
19 </Routes>
20 </BrowserRouter>
21 </div>
22 );
23};
24
25export 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
1const Home = () => {
2 //...other code statements
3 return (
4 <>
5 <div className='buttonGroup'>
6 <button onClick={handlePrint}>Print Resume</button>
7 <Link to='/send/resume' className='sendEmail'>
8 Send via Email
9 </Link>
10 </div>
11 {/*--- other UI elements ---*/}
12 </>
13 );
14};
Add the code snippet below into the src/index.css
file.
1.buttonGroup {
2 padding: 20px;
3 width: 60%;
4 margin: 0 auto;
5 display: flex;
6 align-items: center;
7 justify-content: space-between;
8 position: sticky;
9 top: 0;
10 background-color: #fff;
11}
12.sendEmail {
13 background-color: #f99417;
14 padding: 20px;
15 text-decoration: none;
16 border-radius: 3px;
17}
18.resume__title {
19 margin-bottom: 30px;
20}
21.companyDescription {
22 padding: 15px;
23 border: 1px solid #e8e2e2;
24 border-radius: 3px;
25 margin-bottom: 15px;
26}
27.sendEmailBtn {
28 width: 200px;
29}
30.nestedItem {
31 display: flex;
32 flex-direction: column;
33 width: 50%;
34}
35.nestedItem > input {
36 width: 95%;
37}
Building the application user interface
Update the SendResume
component to display the required form fields as done below.
1import React, { useState } from "react";
2
3const SendResume = () => {
4 const [companyName, setCompanyName] = useState("");
5 const [jobTitle, setJobTitle] = useState("");
6 const [companyDescription, setCompanyDescription] = useState("");
7 const [recruiterName, setRecruiterName] = useState("");
8 const [recruiterEmail, setRecruiterEmail] = useState("");
9 const [myEmail, setMyEmail] = useState("");
10 const [resume, setResume] = useState(null);
11
12 const handleFormSubmit = (e) => {
13 e.preventDefault();
14 console.log("Submit button clicked!");
15 };
16
17 return (
18 <div className='app'>
19 <h1 className='resume__title'>Send an email</h1>
20 <form onSubmit={handleFormSubmit} encType='multipart/form-data'>
21 <div className='nestedContainer'>
22 <div className='nestedItem'>
23 <label htmlFor='recruiterName'>Recruiter's Name</label>
24 <input
25 type='text'
26 value={recruiterName}
27 required
28 onChange={(e) => setRecruiterName(e.target.value)}
29 id='recruiterName'
30 className='recruiterName'
31 />
32 </div>
33 <div className='nestedItem'>
34 <label htmlFor='recruiterEmail'>Recruiter's Email Address</label>
35 <input
36 type='email'
37 value={recruiterEmail}
38 required
39 onChange={(e) => setRecruiterEmail(e.target.value)}
40 id='recruiterEmail'
41 className='recruiterEmail'
42 />
43 </div>
44 </div>
45 <div className='nestedContainer'>
46 <div className='nestedItem'>
47 <label htmlFor='myEmail'>Your Email Address </label>
48 <input
49 type='email'
50 value={myEmail}
51 required
52 onChange={(e) => setMyEmail(e.target.value)}
53 id='myEmail'
54 className='myEmail'
55 />
56 </div>
57 <div className='nestedItem'>
58 <label htmlFor='jobTitle'>Position Applying For</label>
59 <input
60 type='text'
61 value={jobTitle}
62 required
63 onChange={(e) => setJobTitle(e.target.value)}
64 id='jobTitle'
65 className='jobTitle'
66 />
67 </div>
68 </div>
69
70 <label htmlFor='companyName'>Company Name</label>
71 <input
72 type='text'
73 value={companyName}
74 required
75 onChange={(e) => setCompanyName(e.target.value)}
76 id='companyName'
77 className='companyName'
78 />
79 <label htmlFor='companyDescription'>Company Description</label>
80 <textarea
81 rows={5}
82 className='companyDescription'
83 required
84 value={companyDescription}
85 onChange={(e) => setCompanyDescription(e.target.value)}
86 />
87 <label htmlFor='resume'>Upload Resume</label>
88 <input
89 type='file'
90 accept='.pdf, .doc, .docx'
91 required
92 id='resume'
93 className='resume'
94 onChange={(e) => setResume(e.target.files[0])}
95 />
96 <button className='sendEmailBtn'>SEND EMAIL</button>
97 </form>
98 </div>
99 );
100};
101
102export 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.
1import Loading from "./Loading";
2import { useNavigate } from "react-router-dom";
3
4const SendResume = () => {
5 const [loading, setLoading] = useState(false);
6 const navigate = useNavigate();
7
8 if (loading) {
9 return <Loading />;
10 }
11 //...other code statements
12};
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.
1const handleFormSubmit = (e) => {
2 e.preventDefault();
3//👇🏻 form object
4 const formData = new FormData();
5 formData.append("resume", resume, resume.name);
6 formData.append("companyName", companyName);
7 formData.append("companyDescription", companyDescription);
8 formData.append("jobTitle", jobTitle);
9 formData.append("recruiterEmail", recruiterEmail);
10 formData.append("recruiterName", recruiterName);
11 formData.append("myEmail", myEmail);
12//👇🏻 imported function
13 sendResume(formData, setLoading, navigate);
14
15//👇🏻 states update
16 setMyEmail("");
17 setRecruiterEmail("");
18 setRecruiterName("");
19 setJobTitle("");
20 setCompanyName("");
21 setCompanyDescription("");
22 setResume(null);
23};
- 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.
1cd client/src
2mkdir utils
3cd utils
4touch util.js
Create the sendResume
function within the util.js
file as done below. We’ll configure the POST request within the sendResume
function.
1const sendResume = (formData, setLoading, navigate) => {};
2export default sendResume;
Import Axios (already installed) and send the form data to the Node.js server as done below.
1import axios from "axios";
2
3const sendResume = (formData, setLoading, navigate) => {
4 setLoading(true);
5
6 axios
7 .post("http://localhost:4000/resume/send", formData, {})
8 .then((res) => {
9 console.log("Response", res);
10 })
11 .catch((err) => console.error(err));
12};
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.
1app.post("/resume/send", upload.single("resume"), async (req, res) => {
2 const {
3 recruiterName,
4 jobTitle,
5 myEmail,
6 recruiterEmail,
7 companyName,
8 companyDescription,
9 } = req.body;
10
11 //👇🏻 log the contents
12 console.log({
13 recruiterName,
14 jobTitle,
15 myEmail,
16 recruiterEmail,
17 companyName,
18 companyDescription,
19 resume: `http://localhost:4000/uploads/${req.file.filename}`,
20 });
21});
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.
1let workArray = [];
2let applicantName = "";
3let technologies = "";
4
5app.post("/resume/create", upload.single("headshotImage"), async (req, res) => {
6 const {
7 fullName,
8 currentPosition,
9 currentLength,
10 currentTechnologies,
11 workHistory,
12 } = req.body;
13
14 workArray = JSON.parse(workHistory);
15 applicantName = fullName;
16 technologies = currentTechnologies;
17
18 //other code statements...
19});
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:
1app.post("/resume/send", upload.single("resume"), async (req, res) => {
2 const {
3 recruiterName,
4 jobTitle,
5 myEmail,
6 recruiterEmail,
7 companyName,
8 companyDescription,
9 } = req.body;
10
11 const prompt = `My name is ${applicantName}. I want to work for ${companyName}, they are ${companyDescription}
12 I am applying for the job ${jobTitle}. I have been working before for: ${remainderText()}
13 And I have used the technologies such ass ${technologies}
14 I want to cold email ${recruiterName} my resume and write why I fit for the company.
15 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.`;
16
17 const coverLetter = await GPTFunction(prompt);
18
19 res.json({
20 message: "Successful",
21 data: {
22 cover_letter: coverLetter,
23 recruiter_email: recruiterEmail,
24 my_email: myEmail,
25 applicant_name: applicantName,
26 resume: `http://localhost:4000/uploads/${req.file.filename}`,
27 },
28 });
29});
- 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.
1const GPTFunction = async (text) => {
2 const response = await openai.createCompletion({
3 model: "text-davinci-003",
4 prompt: text,
5 temperature: 0.6,
6 max_tokens: 350,
7 top_p: 1,
8 frequency_penalty: 1,
9 presence_penalty: 1,
10 });
11 return response.data.choices[0].text;
12};
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:
1npm 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.
1import emailjs from "@emailjs/browser";
2
3axios
4 .post("http://localhost:4000/resume/send", formData, {})
5 .then((res) => {
6 if (res.data.message) {
7 const {
8 cover_letter,
9 recruiter_email,
10 my_email,
11 applicant_name,
12 resume,
13 } = res.data.data;
14 emailjs
15 .send(
16 "<SERVICE_ID>",
17 "<TEMPLATE_ID>",
18 {
19 cover_letter,
20 applicant_name,
21 recruiter_email,
22 my_email,
23 resume,
24 },
25 "<YOUR_PUBLIC KEY>"
26 )
27 .then((res) => {
28 if (res.status === 200) {
29 setLoading(false);
30 alert("Message sent!");
31 navigate("/");
32 }
33 })
34 .catch((err) => console.error(err));
35 }
36 })
37 .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! ❤️❤️❤️
https://github.com/novuhq/novu