Skip to main content
Version: v2.x

Send a Product Review Request Email

Introduction

Scheduled Triggers allows you to schedule business, or other logic to occur at specific times or intervals. In this guide, we'll explore how to use Scheduled Triggers to send a review request email for a product seven days after the order is delivered. We'll do this by executing this trigger every day at midnight to check whether it has been seven days since an order has been completed, if it has, and the user has not already received a reminder and has not left a review for their purchase, we'll send them an email asking them to do so.

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 such as this, there are three fundamental components to consider:

  • Your data source: In your database, which table contains the value that you want to use to determine whether or not to send the email?
  • Your querying logic: In your webhook, how will you query your database to determine whether or not to send the email? How will you 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: orders, products, and users. The orders table contains the details of all orders placed by customers, including the product ID, customer ID, order status, and delivery date. 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

Head to your the Hasura Console of your project and navigate to 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, e.g., send_review_request_email. Then, enter a webhook URL that will be called when the event is triggered. This webhook will be responsible for sending the review request email and can be hosted anywhere, and written in any language, you like.

The route on our webhook we'll use is /review-request. Below, we'll see what this looks like with a service like ngrok, but the format will follow this template:

https://<your-webhook-url>/review-request
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 🎉

Next, we'll configure the cron expression that will trigger the event. In this example, we want to send requests at midnight. We can do that with the following cron expression:

0 0 * * *

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 review_requests. In the Payload section, enter the following:

{
"trigger_type": "review_requests"
}

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

Also, change the Request Transform Options to POST so that the payload is sent in the request body.

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

Step 3: Create a webhook to handle the request

Whenever a cron job is triggered, Hasura will send a request to the webhook URL you provided. In this example, we're simply going to send a POST request. Our webhook will parse the request, ensure the header is correct, and then query the database to determine which customers should receive a review request email.

Below, we've written an example of webhook in JavaScript. 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('/review-request', 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 timestamp strings for the query
const currentDate = new Date();
const currentUTCDate = new Date(
Date.UTC(
currentDate.getUTCFullYear(),
currentDate.getUTCMonth(),
currentDate.getUTCDate(),
currentDate.getUTCHours(),
currentDate.getUTCMinutes(),
currentDate.getUTCSeconds()
)
);

const sevenDaysAgoMidnight = new Date(currentUTCDate);
sevenDaysAgoMidnight.setDate(currentUTCDate.getUTCDate() - 7);
sevenDaysAgoMidnight.setUTCHours(0, 0, 0, 0);

const sevenDaysAgoEOD = new Date(currentUTCDate);
sevenDaysAgoEOD.setDate(currentUTCDate.getUTCDate() - 7);
sevenDaysAgoEOD.setUTCHours(23, 59, 0, 0);

const sevenDaysAgoMidnightString = sevenDaysAgoMidnight.toISOString();
const sevenDaysAgoEODString = sevenDaysAgoEOD.toISOString();

// Fetch the data from our Hasura instance
async function getRecentOrders() {
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 ReviewRequestQuery($after: timestamptz!, $before: timestamptz!) {
orders(where: {delivery_date: {_gte: $after, _lte: $before}, is_reviewed: {_eq: false}}) {
user {
id
name
email
}
product {
id
name
}
}
}
`,
variables: {
after: sevenDaysAgoMidnightString,
before: sevenDaysAgoEODString,
},
}),
});
const { data } = await response.json();
return data.orders;
}

let orders = await getRecentOrders();

// map over the data and send an email to each customer
async function sendReviewRequests() {
let outcomes = [];
orders.map(async order => {
// Create a message object
const message = {
from: 'SuperStore.com <[email protected]>',
to: `${order.user.name} <${order.user.email}>`,
subject: `We need your review, ${order.user.name.split(' ')[0]}!`,
text: `Hi ${order.user.name.split(' ')[0]}, we hope you're enjoying your ${
order.product.name
}. Please leave us a review!`,
};

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

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

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

await sendReviewRequests(orders);

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

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

You can run the server by running node index.js in your terminal. If you see the message Webhook server is running on port 4000, you're good to go!

Step 4: Test the setup

With your server running, Hasura should start hitting our endpoint. As we set our cron expression to 0 0 * * *, the webhook will be triggered at midnight every day. 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 link to a handy email 🎉

Hasura Scheduled Trigger architecture

Feel free to customize the webhook implementation based on your specific requirements and chosen email sending service. Remember to handle error scenarios, implement necessary validations, and add appropriate security measures to your webhook endpoint.