DEV Community

Cover image for Applying for a new job with React and NodeJS and AI
Nevo David for novu

Posted on • Originally published at novu.co

Applying for a new job with React and NodeJS and AI

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

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 🙌🏻

Job Things

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.

cd client/src/components
touch SendResume.js
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 ---*/}
        </>
    );
};
Enter fullscreen mode Exit fullscreen mode

Code Snippet

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%;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

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
};
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode
  • 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.

cd client/src
mkdir utils
cd utils
touch util.js
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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));
};
Enter fullscreen mode Exit fullscreen mode

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}`,
    });
});
Enter fullscreen mode Exit fullscreen mode

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...
});
Enter fullscreen mode Exit fullscreen mode

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}`,
        },
    });
});
Enter fullscreen mode Exit fullscreen mode
  • 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;
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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));
Enter fullscreen mode Exit fullscreen mode

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

Help

Latest comments (21)

Collapse
 
rizayousefi profile image
Reza

awesome

Collapse
 
delldatascience profile image
Albert Dellor

Great project here.

Collapse
 
delldatascience profile image
Albert Dellor

Great project here. Really useful

Collapse
 
mariamarsh profile image
Maria 🍦 Marshmallow

I think many people underestimate the importance of having a correct and good-looking resume when they apply for jobs, especially as developers. Thank you so much for bringing up this topic in such an unusual manner☺️

Collapse
 
mrakib007 profile image
Rakib Hasan

Great article. Thank you so much.

Collapse
 
titanxtream profile image
Tamojit Bhowmik

Great one ! I was looking for the best clue over email template and I got it here. Thanks ....
It was really helpful

Collapse
 
asimiqbal333 profile image
asimiqbal333

Great efforts 👍

Collapse
 
nevodavid profile image
Nevo David

Thank you so much!

Collapse
 
nishak800 profile image
Nisha Jamin

Great work.

Collapse
 
nevodavid profile image
Nevo David

Thank you so much Nisha!

Collapse
 
th3nolo profile image
ImRobot

Amazing article hopefully I cans land a Job with this new magic.

Collapse
 
nevodavid profile image
Nevo David

You gotta try!

Collapse
 
staubracing profile image
StaubRacing • Edited

I am gong to so do this for a friend!! I am just learning coding and he is a full stack developer. His resume skills are lacking!! haha This would be perfect for me to learn!!

Collapse
 
nevodavid profile image
Nevo David

Sounds awesome!

Collapse
 
sorydi3 profile image
sorydi3

this awesome!!!!

Collapse
 
nevodavid profile image
Nevo David

Thank you so much!

Collapse
 
amn3s1a2018 profile image
Amn3s1a2018 • Edited

I like especially the "such ass" part :)
Although your point are made well: thanks to the AI the cover letters are without "tour de language" like this.

Collapse
 
combarnea profile image
Tomer Barnea

Haha, oh no! @nevodavid I think it should be such as :)

Collapse
 
nevodavid profile image
Nevo David

ChatGPT is smart, such ass is also working :)
Changed it

Collapse
 
empe profile image
Emil Pearce

Great article! Adding a cold emailing feature to a resume builder app using ChatGPT is really helpful. Clear and easy-to-follow instructions. Highly recommended for anyone looking to upgrade their resume with another very useful app :)

Collapse
 
nevodavid profile image
Nevo David

Thank you so much Emil! 🤩