docs: add recipe for coupon expiration

[DOCS-1125]: https://hasurahq.atlassian.net/browse/DOCS-1125?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9959
GitOrigin-RevId: c95393d6ebb914f87043e20645aff99a75948520
This commit is contained in:
Rob Dominguez 2023-07-28 08:11:04 -05:00 committed by hasura-bot
parent 94a2b6348a
commit a5b540f709
3 changed files with 330 additions and 6 deletions

View File

@ -0,0 +1,318 @@
---
title: Using Schedule Triggers for Checking the expiration of a coupon
description: Succinct, tested, and reusable code recipes for common use cases in Hasura.
sidebar_label: Check expiration
keywords:
- hasura
- docs
- recipes
- scheduled triggers
- expiration
- coupon
- automated
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Thumbnail from '@site/src/components/Thumbnail';
# Send a Coupon Expiration Reminder Email
Scheduled Triggers allows you to schedule business, or other logic to occur at specific times or intervals. In this
guide, we'll walk through how to use Scheduled Triggers on an e-commerce type application to send a reminder email to users when their coupon is about to expire. Nudges like this can help increase conversion rates and improve the overall user experience.
## Prerequisites
Before getting started, ensure that you have the following in place:
- A Hasura project, either locally or using [Hasura Cloud](https://cloud.hasura.io/?skip_onboarding=true).
- A working SMTP server or email-sending service that you can integrate with to send emails.
- If you plan on using a webhook endpoint hosted on your own machine with a Hasura project hosted elsewhere, ensure that
you have a tunneling service such as [ngrok](https://ngrok.com/) set up so that a remotely hosted instance 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?
For simplicity, we're assuming two tables in our database: `users` and `coupons`. The `users` table contains the details
of our users, and the `coupons` table contains the details of the coupons that we want to send reminders for.
<details>
<summary>
Click here for the SQL to generate these tables and some seed data.
</summary>
```sql
-- Create the 'users' table
CREATE TABLE public.users (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
email TEXT NOT NULL
);
-- Create the 'coupons' table
CREATE TABLE public.coupons (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES public.users(id),
code VARCHAR(255),
expiration_date DATE
);
-- Seed data for the 'users' table
INSERT INTO public.users (name, email) VALUES
('Daniel Ricciardo', 'user1@example.com'),
('Charles Leclerc', 'user2@example.com'),
('Carlos Sainz', 'user3@example.com');
-- Seed data for the 'coupons' table
INSERT INTO public.coupons (user_id, code, expiration_date) VALUES
(1, 'COUPON3', NOW() + INTERVAL '2 day'),
(2, 'COUPON16', NOW() + INTERVAL '2 day'),
(3, 'COUPON55', NOW() + INTERVAL '2 day');
```
You can copy / paste this into the `RUN SQL` tab in the Hasura Console on the `Data` page. Then, track all relationships
under the `Public` schema on the `Data` page.
**We're generating coupons that expire in two days so that we can test our Scheduled Trigger. If you run this SQL and
come back to the project later, remember to update the `expiration_date` field.**
</details>
## 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`:
<Thumbnail
src="/img/scheduled-triggers/scheduled-triggers_getting-started-guide_2.18.0_click-create.png"
alt="Hasura Scheduled Trigger architecture"
width="1000"
/>
## Step 2: Configure the Scheduled Event
First, provide a name for your trigger, e.g., `send_coupon_expiration_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.
In the example below, if we're using Docker, we'll use a webhook endpoint hosted on our own machine running on port
`4000`. Let's enter the following URL to allow Docker to communicate with the host machine:
```
http://host.docker.internal:4000/expiration_check
```
:::info Tunneling your webhook endpoint
If you're not running your Hasura instance on the same machine as your webhook endpoint, you'll need to use a tunneling
service such as [ngrok](https://ngrok.com/) to expose your webhook endpoint to the internet. This will allow you to
expose a public URL that will forward requests to your local machine and the server we'll configure below.
You'll need to modify your webhook URL to use the public URL provided by ngrok.
:::
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 `coupon_reminder`. In the `Payload` section, enter the following:
```json
{
"trigger_type": "coupon_reminder"
}
```
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`.
<Thumbnail
src="/img/scheduled-triggers/scheduled-triggers_recipes_review-request_auth-header.png"
alt="Hasura Scheduled Trigger architecture"
width="1000"
/>
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 needs to do three things when triggered:
1. Ensure the auth header is correct.
2. Query the database to see which coupons are expiring in two days.
3. For each coupon returned, send an email to the user.
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:
```bash
npm install express nodemailer
```
<details>
<summary>
Then, create a new file called <code>index.js</code> and add the following code:
</summary>
```javascript
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('/expiration_check', 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 twoDaysFromNow = today.setDate(today.getDate() + 2);
const twoDaysFromNowString = new Date(twoDaysFromNow).toISOString().split('T')[0];
// Fetch the data from our Hasura instance
async function getExpiringCoupons() {
const response = await fetch('http://localhost:8080/v1/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// "x-hasura-admin-secret": "<YOUR-ADMIN-KEY>",
},
body: JSON.stringify({
query: `
query GetExpiringCoupons($two_days_from_now: date!) {
coupons(where: {expiration_date: {_lte: $two_days_from_now}}) {
id
code
expiration_date
user {
id
name
email
}
}
}
`,
variables: {
two_days_from_now: twoDaysFromNowString,
},
}),
});
const { data } = await response.json();
return data.coupons;
}
let coupons = await getExpiringCoupons();
// map over the data and send an email to each customer
async function sendCouponReminder(coupons) {
let outcomes = [];
coupons.map(async (coupon) => {
// Create a message object
const message = {
from: 'SuperStore.com <sender@SuperStore.com>',
to: `${coupon.user.name} <${coupon.user.email}>`,
subject: `You've got a coupon expiring soon, ${coupon.user.name.split(' ')[0]}!`,
text: `Yo ${coupon.user.name.split(' ')[0]},\n\nYour coupon code, ${
coupon.code
}, is expiring soon! Use it before ${coupon.expiration_date}.\n\nThanks,\nSuperStore.com`,
};
// Send the message using the Nodemailer transporter
const info = await transporter.sendMail(message);
// Log the message info
console.log(`\nMessage sent to ${coupon.user.name}: ${nodemailer.getTestMessageUrl(info)}`);
// add the info to the outcomes array
outcomes.push({
messageId: info.messageId,
previewUrl: nodemailer.getTestMessageUrl(info),
});
return outcomes;
});
}
await sendCouponReminder(coupons);
// Return a JSON response to the client
res.json({
message: 'Coupons sent!',
});
});
// Start the server
app.listen(4000, () => {
console.log('Server started on port 4000');
});
});
```
</details>
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 🎉
<Thumbnail
src="/img/scheduled-triggers/scheduled-triggers_recipes_review-terminal.png"
alt="Hasura Scheduled Trigger architecture"
width="1000"
/>
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.

View File

@ -24,6 +24,13 @@ customers to return. This recipe shows how to send a request for a product revie
However, understanding the principles of this example will help you to implement your own custom logic for triggering
any event which is based on a time interval. [Check it out](/scheduled-triggers/recipes/product-review.mdx)!
### Send a reminder for coupon expiration
It's common and helpful for sales funnels to send a reminder for coupon expiration a certain period of time before expiration.
This is great for encouraging customers to return. This recipe shows how to send a reminder for coupon expiration 2 days before
the coupon expires. However, understanding the principles of this example will help you to implement your own custom logic for
triggering any event which is based on a time interval. [Check it out](/scheduled-triggers/recipes/check-expiration.mdx)!
### Send a daily summary email for all notifications
In applications with a notification system, it's common to send a daily summary email for all notifications. This recipe

View File

@ -37,7 +37,7 @@ Before getting started, ensure that you have the following in place:
## Our model
When sending transactionary emails such as this, there are three fundamental components to consider:
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?
@ -94,11 +94,10 @@ INSERT INTO public.products (name, description) VALUES
-- Seed data for the 'orders' table
INSERT INTO public.orders (product_id, customer_id, status, delivery_date) VALUES
(1, 1, 'Delivered', '2023-01-05'),
(2, 1, 'In Progress', '2023-02-10'),
(3, 2, 'Delivered', '2023-03-15'),
(1, 3, 'Cancelled', '2023-04-20');
(1, 1, 'Delivered', NOW() - INTERVAl '7 days'),
(2, 1, 'In Progress', NOW() - INTERVAl '14 days'),
(3, 2, 'Delivered', NOW() - INTERVAl '7 days'),
(1, 3, 'Cancelled', NOW() - INTERVAl '30 days');
```
You can copy / paste this into the `RUN SQL` tab in the Hasura Console on the `Data` page. Then, track all relationships