docs: update daily summary recipe to match new schema

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

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10158
GitOrigin-RevId: 4786c43715ec53cdebc671b95443956fa8be46d1
This commit is contained in:
Rob Dominguez 2023-08-22 06:47:53 -05:00 committed by hasura-bot
parent 15f25ed978
commit 3b62671a48

View File

@ -16,6 +16,7 @@ sidebar_position: 2
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Thumbnail from '@site/src/components/Thumbnail';
import SampleAppBlock from '@site/src/components/SampleAppBlock';
# Send a Daily Summary Email
@ -26,19 +27,25 @@ guide, we'll explore how to use Scheduled Triggers to send each user a daily sum
have received. We'll do this by executing this trigger every morning and seeing what new notifications have come through
in the last twenty-four hours. If a user has new notifications, they'll get an email listing them all.
<SampleAppBlock dependent />
## 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).
- 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.
- 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.
:::info 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](https://ngrok.com/) set up so that your Cloud Project can communicate with your local machine.
:::
## 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?
@ -46,54 +53,14 @@ When sending transactionary emails such as this, there are three fundamental com
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 there are two tables in our database: `users` and `notifications`. The `users` table
contains the details of all users, including the user's email address. The `notifications` table contains all
notifications sent to users. Each notification has a `user_id` property that references the user to whom the
notification was sent.
<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 uuid PRIMARY KEY,
email varchar(255) NOT NULL,
name varchar(100) NOT NULL
);
-- create the notifications table
CREATE TABLE public.notifications (
id serial PRIMARY KEY,
user_id uuid NOT NULL REFERENCES public.users(id),
message text NOT NULL,
created_at timestamp DEFAULT now()
);
-- seed data for the users table
INSERT INTO public.users (id, email, name) VALUES
('6f809f39-07a1-4c3b-a5e5-8e6d905366f1', 'user1@example.com', 'Daniel Ricciardo'),
('d0f29c98-c789-4ec0-b84c-1593a6a8d0c2', 'user2@example.com', 'Charles Leclerc'),
('acde545e-32d3-4f28-9475-61ab26a555d9', 'user3@example.com', 'Carlos Sainz');
-- seed data for the notifications table
INSERT INTO public.notifications (user_id, message, created_at) VALUES
('6f809f39-07a1-4c3b-a5e5-8e6d905366f1', 'Your order has been shipped!', NOW() - INTERVAL '30 hours'),
('d0f29c98-c789-4ec0-b84c-1593a6a8d0c2', 'New product added: Smartphone XYZ is now available!', NOW() - INTERVAL '30 hours'),
('acde545e-32d3-4f28-9475-61ab26a555d9', 'Special offer: Get 20% off on all electronics!', NOW() - INTERVAL '30 hours');
```
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.
</details>
Our sample app's database contains, among others, two tables: `users` and `notifications`. The `users` table contains
the details of all users, including the user's email address. The `notifications` table contains all notifications sent
to users. Each notification has a `user_id` property that references the user to whom the notification was sent.
## 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`:
Head to 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"
@ -107,21 +74,31 @@ First, provide a name for your trigger, e.g., `daily_recap_email`. Then, enter a
the event is triggered. This webhook will be responsible for sending the summary 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:
The route on our webhook we'll use is `/daily-summary`. Below, we'll see what this looks like with a service like
[ngrok](https://ngrok.com/), but the format will follow this template:
```
http://host.docker.internal:4000/review-request
```text
https://<your-webhook-url>/daily-summary
```
:::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.
Since our project is running on Hasura Cloud, and our handler will run on our local machine, we'll use ngrok to expose
the webhook endpoint to the internet. This will allow us to expose a public URL that will forward requests to our 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.
After installing ngrok and
[authenticating](https://ngrok.com/docs/secure-tunnels/ngrok-agent/tunnel-authtokens/#:~:text=Once%20you've%20signed%20up,make%20installing%20the%20authtoken%20simple.),
you can do this by running:
```bash
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 9:00
@ -205,7 +182,7 @@ nodemailer.createTestAccount((err, account) => {
});
// Our route for the webhook
app.post('/review-request', async (req, res) => {
app.post('/daily-summary', 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') {
@ -217,29 +194,29 @@ nodemailer.createTestAccount((err, account) => {
// get our date ready for the query
const today = new Date();
const twentyFourHoursPrior = new Date(today.setDate(today.getDate() - 1));
const twentyFourHoursPriorAsTimestamp = twentyFourHoursPrior.toISOString().split('T')[0];
const twentyFourHoursPriorAsTimestamp = twentyFourHoursPrior.toISOString();
// Fetch the data from our Hasura instance
async function getRecentNotifications() {
const response = await fetch('http://localhost:8080/v1/graphql', {
const response = await fetch('<YOUR_CLOUD_PROJECT_ENDPOINT>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// "x-hasura-admin-secret": "<YOUR-ADMIN-KEY>",
'x-hasura-admin-secret': '<YOUR_ADMIN_SECRET>',
},
body: JSON.stringify({
query: `
query DailyNotificationsQuery($start_time: timestamp!) {
users {
query DailyNotificationsQuery($start_time: timestamptz!) {
notifications(where: {created_at: {_gte: $start_time}}) {
id
message
user {
id
email
name
notifications(where: {created_at: {_gte: $start_time}}) {
id
message
}
}
}
}
}
`,
variables: {
start_time: twentyFourHoursPriorAsTimestamp,
@ -247,41 +224,31 @@ nodemailer.createTestAccount((err, account) => {
}),
});
const { data } = await response.json();
return data.users;
return data.notifications;
}
// get our users and filter out the ones with no notifications
let users = await getRecentNotifications();
users = users.filter(user => user.notifications.length > 0);
// helper function to list the notifications in the email
function listNotifications(notifications) {
let list = '';
notifications.map(notification => {
list += `- ${notification.message}\n`;
});
return list;
}
let notifications = await getRecentNotifications();
// map over the data and send an email to each user
async function sendReviewRequests() {
async function sendNotificationSummary(notifications) {
let outcomes = [];
users.map(async user => {
notifications.map(async notification => {
// Create a message object
const message = {
from: 'SuperStore.com <sender@SuperStore.com>',
to: `${user.name} <${user.email}>`,
subject: `You've got new notifications, ${user.name.split(' ')[0]}!`,
text: `Hi ${user.name.split(' ')[0]},\n\nCheck out your recent notifications:\n\n${listNotifications(
user.notifications
)}`,
to: `${notification.user.name} <${notification.user.email}>`,
subject: `You've got new notifications, ${notification.user.name.split(' ')[0]}!`,
text: `Hi ${notification.user.name.split(' ')[0]},\n\nCheck out your recent notifications:\n\n${
notification.message
}\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 ${user.name}: ${nodemailer.getTestMessageUrl(info)}`);
console.log(`\nMessage sent to ${notification.user.name}: ${nodemailer.getTestMessageUrl(info)}`);
// add the info to the outcomes array
outcomes.push({
@ -292,11 +259,11 @@ nodemailer.createTestAccount((err, account) => {
});
}
await sendReviewRequests(users);
await sendNotificationSummary(notifications);
// Return a JSON response to the client
res.json({
message: 'Review requests sent!',
message: 'Notifications sent!',
});
});