mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
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:
parent
94a2b6348a
commit
a5b540f709
318
docs/docs/scheduled-triggers/recipes/check-expiration.mdx
Normal file
318
docs/docs/scheduled-triggers/recipes/check-expiration.mdx
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user