Unlocking Precision Email Delivery in Your Node.js Apps
In the realm of Node.js development, handling email communications effectively is crucial. However, timing is everything when it comes to reaching your audience with the right message at the right moment. That's where SendGrid comes in, empowering you to schedule and cancel emails with ease, directly within your Node.js applications.
Stepping Up Your Email with SendGrid:
SendGrid's versatile API seamlessly integrates with Node.js, providing you with full control over email scheduling and cancellation. Here's a breakdown of how it works:
- Setting Up Your Node.js Environment:
First, make sure you have Node.js and npm (Node Package Manager) installed on your machine.
Create a new directory for your project and navigate into it:
mkdir send-schedule-email
cd send-schedule-email
Initialize a new Node.js project and install all dependencies packages:
npm init -y
npm install express express-async-handler dotenv cors body-parser moment-timezone @sendgrid/client @sendgrid/mail
Create a file named index.js and open it in a code editor. Add the following code:
import bodyParser from "body-parser";
import cors from "cors";
import dotEnv from "dotenv";
import express from "express";
dotEnv.config(); // allow .env file to load
const app = express(); // initializing express app
const corsOptions = {
// cors configuration options
origin: "*",
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions)); // enable cors
app.use(bodyParser.urlencoded({ extended: true })); // enable body parsing
app.use(express.json({ limit: "3.5mb" })); // enable JSON serialization
// Demo Route for testing
app.get("/", (req, res) => {
res.send("Our server is running...");
});
const Port = process.env.PORT || 8080;
// listening to the port
app.listen(Port, console.log("Listening to port ", Port));
You can also install nodemon as a development dependency:
npm install --save-dev nodemon
In your package.json file, add "start", and "dev" scripts to easily run your application in both the production & development environments:
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
Add a ".env" file, and update the environment variables with appropriate values. An example of environment variables is shown below.
PORT=8080
SENDGRID_API_KEY=<YOUR_SENDGRID_API_KEY>
Save your changes and run the application:
npm run dev
Our node app should be running at "http://localhost:8080"
Get your SendGrid API key from your SendGrid account and verify the sender's email.
Now create a controller file named 'sendEmailController.js' and add the following code:
import sendGridClient from "@sendgrid/client";
import sendGridMail from "@sendgrid/mail";
import asyncHandler from "express-async-handler";
import moment from "moment-timezone";
import dotEnv from "dotenv";
dotEnv.config(); // allow .env file to load
sendGridClient.setApiKey(process.env.SENDGRID_API_KEY);
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);
// get batch Id for a scheduled email
const generateBatchId = async () => {
const createBatchRequest = {
url: `/v3/mail/batch`,
method: "POST",
};
try {
const [response, body] = await
sendGridClient.request(createBatchRequest);
return body.batch_id;
} catch (e) {
console.log("Error from generateBatchId: ", e);
return null;
}
};
export const sendEmailIndividual = asyncHandler(async (req, res) => {
const { to, from, subject, content, attachments, send_at } = req.body;
try {
// convert time into GMT-0, send_at should contain a value like "2024-01-03T16:00:00+06:00"
let sendAt = send_at ? moment.tz(send_at, "Etc/UTC") : moment().tz("Etc/UTC");
// Convert the Time to Unix time
sendAt = sendAt.unix();
// Check if scheduling is more than 72 hours in advance
const now = moment();
const sendAtMoment = moment(send_at);
const diffInHours = sendAtMoment.diff(now, "hours");
if (diffInHours > 72) {
return res.status(400).json({
message: "Scheduling more than 72 hours in advance is forbidden.",
});
}
let batchId;
const msg = {
to,
from,
subject,
content,
attachments,
};
if (send_at) {
const send_at_date = moment(send_at);
if (!send_at_date.isBefore(now)) {
// if send_at_date is not in the past
// Create a batch id;
batchId = await generateBatchId();
current_status = "scheduled";
}
msg.send_at = sendAt;
// include batch ID
if (batchId) msg.batch_id = batchId;
}
// Send the email
const sentResult = await sendGridMail.send(msg);
// console.log({ sentResult }); only statusCode, headers
// pick the send-grid message id from the headers
const messageId =
sentResult && sentResult.length > 0
? sentResult[0]?.headers["x-message-id"]
: undefined;
const sendAtDate = new Date(sendAt * 1000);
res
.status(200)
.json({ message: "Email sent successfully.", data:{
messageId, sendAtDate, batchId: msg.batch_id
} });
} catch (err) {
console.log(
"Error from sendEmailIndividual:",
err,
err?.response?.body?.errors
);
if (err.code === 400) {
return res.status(400).json({ message: "Email payload is invalid." });
} else {
return res.status(500).json({ message: "Somthing went wrong." });
}
}
});
// Cancel Scheduled Send
export const cancelScheduledSend = asyncHandler(async (req, res) => {
// Getting batch Id from body
const { batch_id } = req.body;
const cancelBatchRequest = {
url: `/v3/user/scheduled_sends`,
method: "POST",
body: JSON.stringify({
batch_id,
status: "cancel",
}),
};
try {
// Cancel schedule from send grid
await sendGridClient.request(cancelBatchRequest);
return res.status(200).json({ message: "Schedule cancelled" });
} catch (error) {
console.log(
"Error from cancelScheduledSend",
error,
error?.response?.body?.errors
);
if (error.response.body.errors) {
return res
.status(400)
.json({ message: error.response.body.errors[0].message });
}
return res.status(500).json({ message: "Internal Server Error" });
}
});
Let's break it down, first, we have a couple of import statements,
then we initialize the sendGridClient and sendGridMail packages with the send grid API key.
The "generateBatchId" function generates a batch ID for a scheduled send. This function returns the generated batch ID or null ( if any error appears ).
In the "sendEmailIndividual" first we've destructuring assignment
of the request body, then we convert the send_at time to GMT-0 Unix time.
Next, we validate that the send_at is not more than 72 hours in advance. Send grid does not allow more than 72 hours.
Then we prepared the sendGridMail "send" method's payload called msg, we also added the "send_at" and "batch_id" fields to that payload.
Next, we request to send a scheduled mail via SendGrid like this,
const sentResult = await sendGridMail.send(msg);
We can get the message ID from the response headers, and send it back as our API response along with the batchId.
Cancel a scheduled send is much simpler, we just need the batch ID of the scheduled sent-email, here we are getting that with the request payload, we are preparing a request payload as documented in SendGrid.
Next, we made a request, and it was done.
// Getting batch Id from body
const { batch_id } = req.body;
const cancelBatchRequest = {
url: `/v3/user/scheduled_sends`,
method: "POST",
body: JSON.stringify({
batch_id,
status: "cancel",
}),
};
try {
// Cancel schedule from send grid
await sendGridClient.request(cancelBatchRequest);
return res.status(200).json({ message: "Schedule cancelled" });
} catch (error) {
console.log(
"Error from cancelScheduledSend",
error,
error?.response?.body?.errors
);
if (error.response.body.errors) {
return res
.status(400)
.json({ message: error.response.body.errors[0].message });
}
return res.status(500).json({ message: "Internal Server Error" });
We are done with our controller file, let's create a "routes.js" and add the following code.
import express from "express";
import {
sendEmailIndividual,
cancelScheduledSend
} from "./sendEmailController.js";
const router = express.Router();
// Send Individual Email
router
.route("/send-individual-email")
.post(sendEmailIndividual);
// Cancel Scheduled Email
router
.route("/cancel-schedule-email")
.put(cancelScheduledSend);
export default router;
Next, let's introduce these routes to our index.js file before the demo route
import emailRoutes from "./routes.js"; // (new addition)
//... old code
app.use(express.json({ limit: "3.5mb" })); // enable JSON serialization ( old code )
// new addition
// The email routes
app.use("/api", emailRoutes);
// Demo Route for testing ( old code )
app.get("/", (req, res) => {
res.send("Our server is running...");
});
Check that our dev server is running or not, if needed run
npm run dev
Finally, let's test it on Postman.
(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qeyozhshvv65uw74rlt.png)
(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrvuyda9fqdm1m13fe9x.png)
Thank you.
Top comments (0)