mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-26 10:20:54 +03:00
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:
parent
15f25ed978
commit
3b62671a48
@ -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!',
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user