category: How toMar 06, 2023

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.

previously

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 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};
Code Snippet

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

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, 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.

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 workArrayapplicantName, 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:

Curly Brackets

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

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

Related Posts

category: How to

How to Build a Notion-Like Notification Inbox with Chakra UI and Novu

Learn how to build a Notion-inspired real-time notification inbox in React using Chakra UI and Novu's customizable notification component. Includes code examples, styling tips, and a live demo.

Emil Pearce
Emil Pearce
category: How to

Build a Real-time Notification System with Socket.IO and ReactJS

Learn how to build a real-time notification system in a chat app with ReactJS and Socket.io. This step-by-step guide covers setup, event handling, notifications, and best practices.

Emil Pearce
Emil Pearce
category: How to

Case Study: How Novu Migrated User Management to Clerk

Discover how Novu implemented Clerk for user management, enabling features like SAML SSO, OAuth providers, and multi-factor authentication. Learn about the challenges faced and the innovative solutions developed by our team. This case study provides insights into our process, integration strategies that made it possible.

Emil Pearce
Emil Pearce