diff --git a/docs/docs/scheduled-triggers/recipes/daily-summary-email.mdx b/docs/docs/scheduled-triggers/recipes/daily-summary-email.mdx index 40f1edcb502..8f699a27293 100644 --- a/docs/docs/scheduled-triggers/recipes/daily-summary-email.mdx +++ b/docs/docs/scheduled-triggers/recipes/daily-summary-email.mdx @@ -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. + + ## 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. - -
- -Click here for the SQL to generate these tables and some seed data. - - -```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. - -
+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`: /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('', { method: 'POST', headers: { 'Content-Type': 'application/json', - // "x-hasura-admin-secret": "", + 'x-hasura-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 ', - 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!', }); });