Skip to main content
Version: v2.x

Send an Abandoned Cart Reminder Email using Scheduled Triggers

Introduction

Scheduled Triggers allow you to schedule business logic to occur at specific times or intervals.

In this guide, we'll show how to use Scheduled Triggers to send an email reminder to a customer who has added products to their cart but hasn't checked out within one day, otherwise known as an abandoned cart.

The trigger will be executed every hour to check if a user has a cart that hasn't been updated for 24 hours and hasn't already received a reminder. If true, we'll send them an email.

DOCS E-COMMERCE SAMPLE APP

This quickstart/recipe is dependent upon the docs e-commerce sample app. If you haven't already deployed the sample app, you can do so with one click below. If you've already deployed the sample app, simply use your existing project.

Deploy to Hasura Cloud

Prerequisites

Before getting started, ensure that you have the following in place:

  • The docs e-commerce sample app deployed to Hasura Cloud.
  • A working SMTP server or email-sending service that you can integrate with to send emails.
Tunneling your webhook endpoint from your local machine

If you plan on using a webhook endpoint hosted on your own machine, ensure that you have a tunneling service such as ngrok set up so that your Cloud Project can communicate with your local machine.

Our Model

When sending transactional emails like this, consider three essential components:

  • Your data source: Which table in your database contains the value that you will use to determine whether to send the email?
  • Your querying logic: How will your webhook query your database to decide whether to send the email? How will it return information so that you have the correct data to include in the email?
  • Your email templating: How will you generate and send the email containing the information you want to send?

Our sample app's database contains, among others, three tables: cart, cart_items, products, and users.

One shopping cart can have multiple cart_items and there is a one-to-many relationship enabled between these tables. Each cart belongs to one user via the user_id foreign key, and we can use the cart_items updated_at field to determine when last a user interacted with their cart.

The products table contains the details of all products, including the product name and description.

The users table contains the details of all users, including the user's email address.

Step 1: Create the Scheduled Event

Go to your Hasura Console and click the "Events" tab. From there, click on the Cron Triggers item in the sidebar. Then, click Create:

Hasura Scheduled Trigger architecture

Step 2: Configure the Scheduled Event

First, provide a name for your trigger, for example, send_abandoned_cart_email. Enter a webhook URL that will be called when the scheduled event is triggered. This URL should point to the logic you've implemented to:

  • Query the database for users who have abandoned their carts
  • Generate the reminder email
  • Send the email to the correct users

Enter the URL of the webhook to allow Hasura to communicate with it:

https://<your-webhook-url>/abandoned-cart
Tunneling your webhook endpoint to your local machine

You'll need to use a tunneling service such as ngrok to expose a webhook endpoint running on your local machine to the internet and Hasura Cloud. This will give you a public URL that will forward requests to your local machine and the server which we'll configure below.

You'll need to modify your webhook URL to use the public URL provided by ngrok.

After installing ngrok and authenticating, you can do this by running:

ngrok http 4000

Then, copy the Forwarding value for use in our webhook 🎉

In the Cron Schedule field, set the cron expression to 0 * * * *, which means the trigger will be activated every hour.

Our trigger must also have a payload. This payload will be sent to the webhook endpoint when the event is triggered. We don't have to include any data in the payload, but we can if we want to. In this example, we'll simply send a trigger_type property categorizing the event as a check_abandoned_carts. In the Payload section, enter the following:

{
"trigger_type": "check_abandoned_carts"
}

Under Advanced Settings, we can configure the headers that will be sent with the request. We'll add an authentication header to prevent abuse of the endpoint and ensure that only Hasura can trigger the event. Set the Key as secret-authorization-string and the Value as super_secret_string_123.

Hasura Scheduled Trigger architecture

Finally, click the "Add Cron Trigger" button to create the Scheduled Event.

Step 3: Implement the Webhook

Your webhook can be a simple HTTP server that performs the desired tasks. It could be written in any programming language or framework you prefer. The webhook needs to do three main things when triggered:

  1. Query the database to find users who have items in their cart that were added over 24 hours ago and haven't completed the checkout process.
  2. For each user found, generate a reminder email containing the product details.
  3. Send the email.

Below, we've written an example of webhook. As we established earlier, this runs on port 4000. If you're attempting to run this locally, follow the instructions below. If you're running this in a hosted environment, use this code as a guide to write your own webhook.

Init a new project with npm init and install the following dependencies:

npm install express nodemailer
Then, create a new file called index.js and add the following code:
const express = require('express');
const nodemailer = require('nodemailer');

const app = express();

// Create a Nodemailer transporter using Ethereal email service
// Ideally, this configuration would be stored somewhere else
nodemailer.createTestAccount((err, account) => {
if (err) {
console.error('Failed to create a testing account. ' + err.message);
return process.exit(1);
}

// If all goes as planned, here's the console telling us we're 👍
console.log('Credentials obtained, listening on the webhook...');

// Create a transporter object for nodemailer
const transporter = nodemailer.createTransport({
host: 'smtp.ethereal.email',
port: 587,
secure: false,
auth: {
user: account.user,
pass: account.pass,
},
});

// Our route for the webhook
app.post('/abandoned-cart', async (req, res) => {
// confirm the auth header is correct — ideally, you'd keep the secret in an environment variable
const authHeader = req.headers['secret-authorization-string'];
if (authHeader !== 'super_secret_string_123') {
return res.status(401).json({
message: 'Unauthorized',
});
}

// get our date ready for the query
const today = new Date();
const aDayAgo = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const formattedDate = aDayAgo.toISOString();

// Fetch the data from our Hasura instance
async function getRecentNoReminderCarts() {
const response = await fetch('<YOUR_CLOUD_PROJECT_ENDPOINT>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-hasura-admin-secret': '<YOUR_ADMIN_SECRET>',
},
body: JSON.stringify({
query: `
query EmailsOfUsersWithAbandonedCarts($updated_date: timestamptz!) {
carts(where: {_and: {is_reminder_sent: {_eq: false}, cart_items: {updated_at: {_lt: $updated_date}}}}) {
id
created_at
user_id
is_reminder_sent
user {
id
email
name
}
}
}
`,
variables: {
updated_date: formattedDate,
},
}),
});
const { data } = await response.json();
const { carts } = data;
return carts || [];
}

let carts = await getRecentNoReminderCarts();

console.log('carts: ', JSON.stringify(carts, null, 2));

// map over the data and send an email to each customer
async function sendAbandonedCartReminders(carts) {
let outcomes = [];
for (let i = 0; i < carts.length; i++) {
const cart = carts[i];
// Create a message object
const message = {
from: 'SuperStore.com <[email protected]>',
to: `${cart.user.name} <${cart.user.email}>`,
subject: `You've left items in your cart, ${cart.user.name.split(' ')[0]}!`,
text: `Hi ${
cart.user.name.split(' ')[0]
}, it seems you've left some items in your cart. Don't forget to check out!`,
};

// Send the message using the Nodemailer transporter
const info = await transporter.sendMail(message);

// Log the message info
console.log(`\nMessage sent to ${cart.user.name}: ${nodemailer.getTestMessageUrl(info)}`);

// add the info to the outcomes array
outcomes.push({
messageId: info.messageId,
previewUrl: nodemailer.getTestMessageUrl(info),
});
}
return outcomes;
}

await sendAbandonedCartReminders(carts);

// Return a JSON response to the client
res.json({
message: 'Abandoned cart reminders sent!',
});

// Here we would run a query to update the database to mark the carts as having received a reminder but we will
// leave this out for ease of demonstration.
});

// Start the server
app.listen(4000, () => {
console.log('Server started on port 4000');
});
});

This script connects to a Postgres database, finds the users who have abandoned their carts, generates a reminder email for each user, and sends it. The script uses the Nodemailer package to send emails.

If you see the message Webhook server is running on port 4000, you're good to go!

Step 4: Test your Setup

Now that your Scheduled Trigger and webhook are set up, you can test it by simulating an abandoned cart situation.

With your server running, Hasura should start hitting our endpoint. As we set our cron expression to 0 * * * *, the webhook will be triggered every hour. We don't want to wait that long to test it. For now, update the expression to * * * * * to trigger the webhook every minute. Then, check out your invocation logs in the Hasura Console to verify that the webhook was triggered successfully and your terminal to see the outputted information and a handy link to a the mock email 🎉

Click here to see an example of the console log of the webhook.
carts:  [
{
"id": "e2f27008-673d-11ed-8a24-7224baf239e5",
"created_at": "2023-08-20T16:22:21.68884+00:00",
"user_id": "7cf0a66c-65b7-11ed-b904-fb49f034fbbb",
"is_reminder_sent": false,
"user": {
"id": "7cf0a66c-65b7-11ed-b904-fb49f034fbbb",
"email": "[email protected]",
"name": "Sean"
}
},
{
"id": "e6e0edc0-673d-11ed-8a25-7224baf239e5",
"created_at": "2023-08-20T12:22:21.68884+00:00",
"user_id": "82001336-65b7-11ed-b905-7fa26a16d198",
"is_reminder_sent": false,
"user": {
"id": "82001336-65b7-11ed-b905-7fa26a16d198",
"email": "[email protected]",
"name": "Rob"
}
},
{
"id": "ea226f5e-673d-11ed-8a26-7224baf239e5",
"created_at": "2023-08-20T02:22:21.68884+00:00",
"user_id": "86d5fba0-65b7-11ed-b906-afb985970e2e",
"is_reminder_sent": false,
"user": {
"id": "86d5fba0-65b7-11ed-b906-afb985970e2e",
"email": "[email protected]",
"name": "Marion"
}
},
{
"id": "ee2c0948-673d-11ed-8a27-7224baf239e5",
"created_at": "2023-08-20T20:22:21.68884+00:00",
"user_id": "8dea1160-65b7-11ed-b907-e3c5123cb650",
"is_reminder_sent": false,
"user": {
"id": "8dea1160-65b7-11ed-b907-e3c5123cb650",
"email": "[email protected]",
"name": "Sandeep"
}
},
{
"id": "f11e43aa-673d-11ed-8a28-7224baf239e5",
"created_at": "2023-08-20T20:22:21.68884+00:00",
"user_id": "9bd9d300-65b7-11ed-b908-571fef22d2ba",
"is_reminder_sent": false,
"user": {
"id": "9bd9d300-65b7-11ed-b908-571fef22d2ba",
"email": "[email protected]",
"name": "Abby"
}
}
]

Message sent to Sean: https://ethereal.email/message/ZOOv8vpfkoUaaaERZOOwe8.vdLF4IYAAAAAAC9ZopFVSeISEbido2clhdIY

Message sent to Rob: https://ethereal.email/message/ZOOv8vpfkoUaaaERZOOwfbf8ObJ559KGAAAADNWlylfrS2nLoNm08jGBs8U

Message sent to Marion: https://ethereal.email/message/ZOOv8vpfkoUaaaERZOOwf8.vdLF4IYADAAAADeBzopphA4UxwgtGz41In74

Message sent to Sandeep: https://ethereal.email/message/ZOOv8vpfkoUaaaERZOOwgfNxowjZihTyAAAADnGmSjpMKPsxQbwhwmVjlcE

Message sent to Abby: https://ethereal.email/message/ZOOv8vpfkoUaaaERZOOwg.NxowjZihT1AAAAD-Lx3fc0Cofq6XFMj6bn69E

Conclusion

In this guide, we've shown how to use Hasura Scheduled Triggers to automate sending reminder emails for abandoned shopping carts. This can be a powerful tool to drive conversion and revenue in e-commerce applications.