Send Personalized Emails using Gemini API, SendGrid and Node.js

June 12, 2024
Written by
Avinash Prasad
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Send Personalised Emails using Gemini API, SendGrid and Node.js

Sending generic, one-size-fits-all emails is no longer an effective strategy as it fails to resonate with the recipient on a personal level. With the use of generative AI you can accelerate the process of crafting and sending personalised emails at scale.

Large language models, such as Gemini pro, allow you to generate highly contextual and natural-sounding text tailored to each recipient's unique profile. This approach saves time and ensures a level of personalization that would be nearly impossible to achieve manually.

It is essential to use AI-generated content ethically and responsibly. The content generated by large language models like Gemini pro should be reviewed and personalised before sending to ensure authenticity.

In this tutorial, you will build a simple application that uses the Gemini pro model to generate personalised emails based on your prompt, allows you to edit the email before finalising it and use SendGrid’s email delivery system to send the email. You will use Node.js for server-side logic and React.js for a simple frontend.

Prerequisites

In order to follow along with this tutorial, you will need the following:

Setting up the project

Start by creating a minimal React.js project using nano-react-app with the following command:

npx nano-react-app ai-email-dashboard

Change the working directory to your new React project, ai-email-dashboard,install dependencies with npm install and run the application to make sure everything is fine:

cd ai-email-dashboard
npm install
npm start

Building the Frontend

Construct a basic user interface in your App.jsx file, that has a heading that says Dashboard, an input box for writing the prompt, a Generate button, a preview section that displays the generated email and allows you to edit it, and a Send Email button:

import React from "react";

const App = () => {
  return (
	<>
  	<div className="app">
    	<h1>✎ Dashboard</h1>

    	<div className="input-container">
      	<input placeholder="Write a prompt..." />
      	<button>Generate</button>
    	</div>

    	<div className="preview">
      	<p>Edit:</p>

      	<div className="subject-line">
        	Subject: <input className="subject-input" defaultValue="Example Email - Craft Compelling Emails with AI" />
      	</div>

      	<textarea className="body-text"
        	defaultValue={
          	`Hi there,

Are you tired of writer's block when it comes to crafting emails? Do you ever wish you could write clear, concise, and impactful emails faster?

This AI-powered email generation tool can helps you write better emails in less time.`
        	}
      	/>
    	</div>

    	<p><b>Email List: </b>testuser@gmail.com, testuser2@gmail.com</p>

    	<button className="send">Send Email</button>

  	</div>
	</>
  )
}

export default App;

You can style this React interface, using an external stylesheet. Create a file index.css and add this code:

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');

* {
  font-family: Inter;
}

body {
  margin: 0;
  padding: 0;
  width: 100vw;
  height: 100vh;
  background-color: #e5e5e5;
  color: #3d3d3d;
  display: flex;
  justify-content: center;
}

p {
  font-weight: 200;
  font-size: 14;
}

.app {
  width: 50vw;
  margin-top: 5vw;
  height: auto;
  display: flex;
  flex-direction: column;
}

@media only screen and (max-width: 1280px) {
  .app {
	width: 90vw;
  }
}

.input-container {
  display: flex;
  width: 100%;
  margin-bottom: 20px;
  border: solid 0.5px #cacaca;
  box-sizing: border-box;
  border-radius: 6px;
  box-shadow: rgba(0, 0, 82, 0.15) 0 2px 4px;
  overflow: hidden;
}

.input-container input {
  border: none;
  padding: 13px 14px;
  box-sizing: border-box;
  font-size: 15px;
  outline: none;
  width: 90%;
  font-weight: 200;

}

.input-container button {
  min-width: 10%;
  border: none;
  border-left: 1px solid #cacaca;
  background-color: #fff;
  color: #777;
  font-weight: bold;
  cursor: pointer;
}

.input-container button:hover {
  background-color: #eeeeee;
}

.preview {
  padding: 13px 14px;
  border: solid 0.5px #cacaca;
  background-color: #fff;
  margin: 5px;
  font-size: 16px;
  font-weight: 200;
  border-radius: 6px;
  margin-bottom: 20px;
}

.subject-line {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.subject-input {
  width: 100%;
  height: auto;
  overflow: hidden;
  resize: none;
  padding: 5px;
  margin-left: 7px;
  border: solid 0.5px #dedede;
  box-sizing: border-box;
}

.body-text {
  width: 100%;
  height: 150px;
  resize: none;
  padding: 10px;
  box-sizing: border-box;
  border: solid 0.5px #dedede;
}

.send {
  background-color: #47a3ea;
  color: white;
  padding: 13px 14px;
  border-radius: 6px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  cursor: pointer;
  border: none;
}

.send:hover {
  transition: 0.5s;
  background-color: #3e89c1;
}

To apply this css styling, add an import in the index.js file in the /src directory:

import './index.css';

To make sure the application is running, go to http://localhost:5173/ and it should look like this:

User Interface of AI Email Dashboard Application
User Interface of AI Email Dashboard Application

Setting up an Express Backend

You can create a simple backend server using Express.js for creating APIs and handling server-side logic. Start with installing these dependencies:

npm install nodemon dotenv nodemailer cors express @google/generative-ai
  • nodemon: It is a development utility that automatically restarts your Node.js application whenever you make changes to the code.
  • dotenv: This package allows you to load environment variables from a .env file, keeping sensitive information like API keys out of your codebase.
  • nodemailer: This package simplifies sending emails from your Express.js backend.
  • @google/generative-ai: To use the Gemini API in your own application, you need to install the GoogleGenerativeAI package for Node.js

Start by creating a new file named server.js in the root directory of your application and add:

const express = require('express')
const nodemailer = require("nodemailer")
const cors = require('cors')
const bodyParser = require('body-parser');
const app = express()
const PORT = 8000;

app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
require('dotenv').config()

app.listen(PORT, () => {
	console.log(`Listening to Port ${PORT}`)
})

This code sets up a basic Express.js server with functionalities for parsing incoming data (JSON and URL-encoded forms) and handling CORS. It also allows you to load environment variables for secure configuration and starts the server on the port 8000.

Start the the backend server with the following command:

npx nodemon server.js

Once the server starts successfully, you should see 'Listening to Port 8000' in your terminal.

Obtain the Gemini API Key

To use the Gemini API, you first need to obtain the API key from https://aistudio.google.com/app/apikey

Once you have it, store it securely in a .env file in the root directory. Create a .env if you don’t have one and add:

GEMINI_API_KEY=[YOUR API KEY]

Using Gemini API in your Backend

Add this in your server.js to access the Gemini API key and import the GoogleGenerativeAI package:

const { GoogleGenerativeAI } = require('@google/generative-ai')

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY)

Create a /generate route handler, in server.js, to generate text using Gemini:

app.post("/generate", async(req, res) => {
	const model = genAI.getGenerativeModel({model: "gemini-pro"})

	const prompt = req.body.prompt + " Make the first line the subject line (but no need to start with 'Subject: ') and rest of the text the body of the email."

	const result = await model.generateContent(prompt);
	const response = await result.response;
	const text = response.text();

	res.send({'text': text})
})

This code defines a route handler for /generate that listens for POST requests. When a request arrives, it retrieves the prompt sent from the frontend in the request body.

Notice the prompt is modified before sending it to the generative model. It specifies that the first line of the generated text should become the subject line of the email, and the remaining text should form the body. This will simplify the process of separating the subject and body later.

The code uses the gemini-pro generative model to generate text based on the prompt. Finally, the generated text is extracted and sent back to the frontend as a JSON response with a key named text.

Make sure your express server is running and use Postman to test the /generate API route:

Using Postman to test /generate route
Using Postman to test /generate route

In the query parameters, set the “prompt” key with your prompt as the value, and send a post request to http://localhost:8000/generate. The output should be the text generated by Gemini AI based on your prompt.

Fetching AI Generated Text from the Backend

In your App.jsx create three state variables, subject and body for our email, and prompt to store the prompt. Add this right above the return function:

const [subject, setSubject] = useState("Example Email - Craft Compelling Emails with AI")
const [body, setBody] = useState(`Hi there,

Are you tired of writer's block when it comes to crafting emails? Do you ever wish you could write clear, concise, and impactful emails faster?
 
This AI-powered email generation tool can helps you write better emails in less time.`
          	)
const [prompt, setPrompt] = useState("")

Since the state variables subject and body already hold the initial values, you can directly set the value attribute of the input elements to these variables, and set an onChange listener to detect changes and update the state variables:

<div className="subject-line">
  Subject: <input
  className="subject-input"
  value={subject}
  onChange={(e) => setSubject(e.target.value)}/>
 
</div>

<textarea className="body-text"
  value={body}
  onChange={(e) => setBody(e.target.value)}
/>

Now, define a new function called generateEmail, right before the return function. This function will be responsible for fetching data from the /generate API:

const generateEmail = () => {
	if(!prompt) {
  		return
	}

	try {
  	    const options = {
    		method: 'POST',
    		headers: {
      		‘content-type': 'application/json'
    		},
    		body: JSON.stringify({
      			prompt: prompt,
    		})
  	}

  	fetch('http://localhost:8000/generate', options)
    		.then(res => res.json())
    		.then(data => {
      			const lines = data.text.split("\n")
      			setSubject(lines[0])
      			setBody(lines.slice(1).join('\n') + "\n\nGenerated for you by Gemini AI")
    	})
    	.catch(error => {
      	console.error(error);
    	});

  	setPrompt("")

	} catch (error) {
  		console.error(error)
  		setError("Something went wrong! Please try again later.")
	}
  }

This function interacts with a backend API to generate an email based on the provided prompt and updates the component's state with the retrieved subject and body content

This tutorial does not endorse using AI to pass off generated content as your own work. Transparency is crucial. The code adds a signature which says 'Generated for you by Gemini AI' in AI-generated emails to maintain honesty with recipients.

Set an onClick listener for the Generate button to trigger the generateEmail() function

<button onClick={generateEmail}>Generate</button>

To test the application, make sure your React application and Node.js server are running concurrently, enter a prompt and click the Generate button:

Testing text generating using Gemini pro model
Testing text generating using Gemini pro model

The app should display the generated text in the input areas and allow the user to edit the text. The final step is to use SendGrid to deliver the email.

Delivering the Email with SMTP Server

SMTP stands for Simple Mail Transfer Protocol. It is a standardised protocol used for sending and receiving email messages between email servers. For your email generator to work seamlessly, you need a reliable email delivery service. Twilio SendGrid is an excellent choice for this purpose.

Obtaining the SendGrid API key

  • Go to the SendGrid website.
  • For this tutorial, select a free plan, provide required details, and sign up!

After signing up, navigate to the Dashboard, and on the left sidebar go to: Settings > Sender Authentication.

Perform the sender authentication (single sender verification in this case) by verifying your email address. After verification, create a new sender by filling in the required details. This is the email that will be used by the SMTP Server to send emails.

Go to the API Keys section in your SendGrid Dashboard by heading to the settings tab on the left-hand side and clicking API Keys.

Click the blue “Create API Key” button on the top right and a form will appear.

Set a name for your API key and click the blue Create & View button. Copy your API key and store it in the .env file in your root directory:

GEMINI_API_KEY=[YOUR API KEY]
SENDGRID_API_KEY=[YOUR API KEY]

Building the /send-email Route

A transporter is a Nodemailer object that acts as a bridge between your Node.js application and the SendGrid Email service. In your server.js, right above the /generate route, add a transporter:

let transporter = nodemailer.createTransport({
	host: 'smtp.sendgrid.net',
	port: 587,
	auth: {
    	user: "apikey",
    	pass: process.env.SENDGRID_API_KEY
	}
})

Below this, write a new route called /send-email:

app.post("/send-email",(req, res) => {
	transporter.sendMail({
    		from: "[YOUR SENDER EMAIL ADDRESS]",
    		to: "[RECIPIENT EMAIL ADDRESS]",
    		subject: req.body.subject,
    		text: req.body.body,
	}, function(error, info) {
    	if (error) {
        		console.log(error);
        		req.send({'status': `Error ${error}`})
    	} else {
        		console.log('Email sent: ' + info.response)
        		req.send({'status': "Success! Email Sent: " + info.response})
    	}
	})
})

This route retrieves the email details (subject and body) from the request body, uses the configured transporter to send the email, and sends a response back to the client indicating success or failure.

To keep things simple in this tutorial, the recipient's email address is directly entered in the code. However, you might have noticed the email list option in the user interface, located above the Send button. With some additional coding and logic, you can implement a feature that allows users to select the recipient's email from a list instead of having it hardcoded.

Final Step

The last step is to send the body and subject to the /send-email route on your server for the email to be delivered. In your App.jsx, right above the return function, create a function called sendEmail():

const sendEmail = () => {
	if (!subject || !body) {
  		return
	}

	try {
  		const options = {
    		method: 'POST',
    	headers: {
      		'content-type': 'application/json'
    	},
    	body: JSON.stringify({
      		subject: subject,
      		body: body,
    		})
  	}

  	fetch('http://localhost:8000/send-email', options)
    	.then(res => res.json())
    	.then(data => {
      		console.log(data.status)
    	})
    	.catch(error => {
      		console.error(error);
    	});

  	setPrompt("")

	} catch (error) {
  		console.error(error)
  		setError("Something went wrong! Please try again later.")
	}
  }

This code defines a function sendEmail that sends the email contents if subject and body are provided. It prepares the email data in JSON format and sends a POST request to the /send-email route.

Don’t forget to add an OnClick listener in the Send Email button to trigger the sendEmail function:

<button className="send" onClick={sendEmail}>Send Email</button>

Test the application by clicking on the Send Email button. If the email is sent successfully, your server's terminal should display a confirmation message:

Email sent: 250 Ok: queued as [UNIQUE ID]

Conclusion

Congratulations! You have successfully built an application that leverages a Large Language Model, and SendGrid to generate and send personalised emails.

The source code for this project can be found here: AI Email Dashboard

What’s next?

  • Add a feature to send email to multiple recipients.
  • Improve upon the design of the application.
  • Implement analytics such as open rates, click-through rates, and conversion rates.

Avinash Prasad is a Software Developer and Content Creator. He has a passion for simplifying complex technical topics, making them accessible to a wide audience. He is the host of DevStories podcast . He can be reached via email and Twitter .