Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Gửi thông báo qua email chính xác khi bạn muốn:Lập kế hoạch nhận biết múi giờ với QStash

Tại docsly, chúng tôi đã ra mắt tính năng mới để gửi thông báo qua email tới người dùng kèm theo bản tóm tắt tất cả phản hồi họ nhận được trong tuần hoặc tháng trước. Gửi email không phải là vấn đề mới nhưng chúng tôi muốn cung cấp trải nghiệm tốt nhất cho người dùng về vấn đề này nên chúng tôi quyết định rằng tất cả email phải được gửi theo múi giờ của người dùng để tránh gửi email vào giờ lẻ. Chúng tôi cũng muốn cung cấp cho người dùng khả năng chọn tần suất nhận email mà họ muốn. Chúng tôi cũng muốn cung cấp cho người dùng khả năng hủy thông báo qua email đã lên lịch bất kỳ lúc nào.

Việc triển khai giải pháp này rất khó khăn vì:

  1. Chúng tôi không muốn lưu trữ múi giờ của người dùng trong cơ sở dữ liệu của mình.
  2. Chúng tôi cũng không muốn chạy công việc định kỳ hàng phút hoặc hàng giờ để kiểm tra xem đã đến lúc gửi email hay chưa.
  3. Chúng tôi cũng muốn cho phép người dùng hủy thông báo qua email đã lên lịch.

Vì vậy, chúng tôi đã nghĩ ra một giải pháp độc đáo:Lên lịch công việc định kỳ theo múi giờ của người dùng và sau đó hủy công việc định kỳ khi người dùng hủy thông báo qua email. Nhưng vẫn còn một câu hỏi:làm thế nào?

Chúng tôi đã sử dụng Upstash làm cửa hàng Redis và chúng tôi nhận thấy rằng QStash cũng hỗ trợ tính năng Lịch trình. Tìm hiểu sâu hơn một chút, chúng tôi thấy rằng QStash cũng hỗ trợ các biểu thức CRON. Vì vậy, chúng tôi quyết định sử dụng QStash để lên lịch cho các công việc định kỳ.

Trong bài viết này, chúng tôi sẽ hướng dẫn bạn quy trình lên lịch gửi email theo múi giờ của người dùng bằng cách sử dụng QStash và Upstash Redis trong ứng dụng Next.js. Bạn cũng có thể tìm thấy mã nguồn hoàn chỉnh trên GitHub.

Xây dựng ứng dụng Next.js để lên lịch gửi email theo múi giờ của người dùng

Điều kiện tiên quyết

Để làm theo hướng dẫn này, bạn sẽ cần:

  • Tài khoản Upstash
  • Môi trường phát triển Node.js

Thiết lập dự án

Để bắt đầu, hãy tạo một dự án Next.js mới bằng lệnh sau:

npx create-next-app qstash-email-scheduling

Tiếp theo, cài đặt các phần phụ thuộc sau để tương tác với Upstash:

npm install --save @upstash/redis axios

Tạo một .env.local mới vào thư mục gốc của dự án và thêm các biến môi trường sau từ tài khoản Upstash của bạn:

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
QSTASH_URL=
QSTASH_TOKEN=
QSATSH_CURRENT_SIGNING_KEY=
QSATSH_NEXT_SIGNING_KEY=

Tổng quan về giải pháp

Trước khi triển khai mã, chúng ta hãy xem tổng quan về giải pháp. Chúng tôi sẽ tạo ba tuyến API Next.js:

  • POST /api/schedule-cron - để lên lịch công việc định kỳ email theo múi giờ của người dùng.
  • POST /api/cancel-schedule - để hủy công việc định kỳ email đã lên lịch.
  • POST /api/send-email - được kích hoạt bởi công việc định kỳ đã lên lịch để gửi email.

Ngoài các tuyến API, chúng tôi cũng sẽ tạo một biểu mẫu đơn giản để thu thập thời gian mong muốn nhận email của người dùng.

Tạo giao diện người dùng

Để tạo giao diện người dùng, chúng ta sẽ tạo một trang mới tại app/page.tsx với đoạn mã sau:

"use client";
 
import { useState } from "react";
 
import axios from "axios";
 
export default function Home() {
 const userId = "tony-stark-11";
 const [selectedTime, setSelectedTime] = useState("10:00");
 
 async function createEmailNotificationSchedule() {
 try {
 await axios.post(
 "/api/schedule-cron",
 {
 userId,
 selectedTime,
 utcOffset: new Date().getTimezoneOffset(),
 },
 {
 headers: {
 "Content-Type": "application/json",
 },
 },
 );
 alert("Email notification scheduled");
 } catch (e) {
 console.log("Client side error", e);
 alert("Error scheduling email notification");
 }
 }
 
 async function cancelEmailNotificationSchedule() {
 try {
 await axios.post(
 "/api/cancel-schedule",
 {
 userId,
 },
 {
 headers: {
 "Content-Type": "application/json",
 },
 },
 );
 alert("Email notification schedule cancelled");
 } catch (e) {
 console.log("Client side error", e);
 alert("Error scheduling email notification");
 }
 }
 
 return (
 <main className="mx-auto flex min-h-screen max-w-md flex-col justify-center p-24">
 <h1 className="mb-4 text-xl font-bold text-neutral-600">
 Email Notification for {userId}
 </h1>
 Send daily email summary at:
 <select
 onChange={(e) => setSelectedTime(e.target.value)}
 className="mt-4 h-12 w-64 rounded-lg border-2 border-neutral-600 bg-neutral-800 p-2
 text-white"
 >
 {new Array(24).fill(0).map((_, i) => {
 const time = i < 10 ? `0${i}:00` : `${i}:00`;
 return (
 <option key={i} value={time}>
 {time}
 </option>
 );
 })}
 </select>
 <button
 className="mt-4 rounded bg-green-700 px-4 py-2 text-white"
 onClick={createEmailNotificationSchedule}
 >
 Schedule
 </button>
 <button
 className="mt-4 rounded bg-red-500 px-4 py-2 text-white"
 onClick={cancelEmailNotificationSchedule}
 >
 Cancel Schedule
 </button>
 </main>
 );
}

Đoạn mã trên tạo một biểu mẫu có menu thả xuống để người dùng đặt thời gian ưa thích để nhận bản tóm tắt email hàng ngày. Họ có thể lên lịch hoặc hủy các thông báo này và ứng dụng sẽ liên lạc với máy chủ bằng các yêu cầu HTTP POST. Chúng ta sẽ tạo điểm cuối HTTP trong các phần sau.

Gửi thông báo qua email chính xác khi bạn muốn:Lập kế hoạch nhận biết múi giờ với QStash

Lên lịch công việc định kỳ email

Gửi thông báo qua email chính xác khi bạn muốn:Lập kế hoạch nhận biết múi giờ với QStash

Hãy bắt đầu bằng cách tạo POST /api/schedule-cron tuyến đường. Tuyến đường này sẽ được sử dụng để lên lịch công việc định kỳ email theo múi giờ của người dùng. Chúng tôi sẽ sử dụng thư viện QStash để lên lịch công việc định kỳ.

import { NextApiRequest, NextApiResponse } from "next";
 
import { Redis } from "@upstash/redis";
import axios from "axios";
 
export const QSTASH_CONFIG = {
 QSTASH_URL: process.env.QSTASH_URL,
 QSTASH_TOKEN: process.env.QSTASH_TOKEN,
 QSTASH_CURRENT_SIGNING_KEY: process.env.QSTASH_CURRENT_SIGNING_KEY,
};
 
export const upstash = new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL!,
 token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
 
// Edit this endpoint to match your domain
const SUMMARY_ENDPOINT = "https://<your-domain>/api/send-email";
 
export default async function scheduleSummary(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 console.log("========SCHEDULE SUMMARY========");
 if (req.method !== "POST") {
 return res.status(400).json({ message: "bad request" });
 }
 const { body } = req;
 
 const { userId, selectedTime, utcOffset } = body;
 
 const emailScheduleKey = `email-schedule-${userId}`;
 
 const scheduleId = await upstash.get(emailScheduleKey);
 
 // remove existing schedule before creating a new one
 if (scheduleId) {
 try {
 await axios.delete(
 `https://qstash.upstash.io/v1/schedules/${scheduleId}`,
 {
 headers: {
 Authorization: `Bearer ${QSTASH_CONFIG.QSTASH_TOKEN}`,
 },
 },
 );
 } catch (e) {
 console.log("Schedule not found in QStash ");
 }
 await upstash.del(emailScheduleKey);
 }
 
 const [hour, min] = convertToUTC(selectedTime, utcOffset).split(":");
 const selectedCron = `${min} ${hour} * * *`;
 
 // create and store new schedule
 try {
 const { data, status } = await axios.post(
 `${QSTASH_CONFIG.QSTASH_URL}${SUMMARY_ENDPOINT}`,
 { userId },
 {
 headers: {
 "Content-Type": "application/json",
 Authorization: `Bearer ${QSTASH_CONFIG.QSTASH_TOKEN}`,
 "Upstash-Cron": selectedCron,
 },
 },
 );
 console.log({ data, status });
 if (data.scheduleId) {
 await upstash.set(emailScheduleKey, data.scheduleId);
 }
 } catch (e) {
 console.log({ e });
 }
 
 return res.status(200).json({ message: "success" });
}
 
function convertToUTC(timeString: string, utcOffset: number) {
 const [hours, minutes] = timeString.split(":").map(Number);
 const timeInMinutes = hours * 60 + minutes;
 const utcTimeInMinutes = (timeInMinutes + utcOffset + 1440) % 1440;
 const utcHours = Math.floor(utcTimeInMinutes / 60);
 const utcMinutes = utcTimeInMinutes % 60;
 return `${utcHours.toString().padStart(2, "0")}:${utcMinutes
 .toString()
 .padStart(2, "0")}`;
}

Cốt lõi của mã là scheduleSummary chức năng, là điểm cuối API. Nó xử lý các yêu cầu POST đến và thực hiện các bước sau:

  1. Xác thực phương thức yêu cầu, đảm bảo đó là yêu cầu POST.
  2. Trích xuất dữ liệu từ nội dung yêu cầu, bao gồm userId, selectedTime và utcOffset.
  3. Tạo khóa cho lịch gửi email của người dùng trong cơ sở dữ liệu Upstash.
  4. Truy xuất mọi lịch trình hiện có của người dùng và xóa lịch trình đó khỏi QStash.
  5. Chuyển đổi thời gian đã chọn của người dùng sang định dạng UTC bằng cách sử dụng convertToUTC hàm lấy chuỗi thời gian và độ lệch UTC, chuyển đổi thời gian sang định dạng UTC trong khi tính toán độ lệch.
  6. Tạo lịch mới để gửi bản tóm tắt email bằng chức năng lập lịch của QStash và thêm userId là tải trọng mà /api/send-email điểm cuối nhận được.
  7. Lưu trữ ID lịch trình mới được tạo trong cơ sở dữ liệu Upstash.

Gửi email tóm tắt

POST /api/send-email tuyến đường sẽ được kích hoạt bởi công việc định kỳ đã lên lịch để gửi email. Chúng ta sẽ sử dụng @upstash/qstash/nextjs thư viện để xác minh chữ ký của yêu cầu. Điều này sẽ đảm bảo rằng yêu cầu đến từ QStash chứ không phải từ bất kỳ nguồn nào khác.

POST /api/send-email hàm nhận được userId trong phần thân yêu cầu. Bạn có thể triển khai chức năng chuẩn bị và gửi email dựa trên userId .

import { NextApiRequest, NextApiResponse } from "next";
 
import { verifySignature } from "@upstash/qstash/nextjs";
 
async function handler(request: NextApiRequest, res: NextApiResponse) {
 console.log("==========Project summary handler==========");
 if (request.method !== "POST") {
 return res.status(400).json({ message: "bad request" });
 }
 const { body } = request;
 const { userId } = body;
 
 // prepare and send email
 
 return res.status(200).json({ message: "success" });
}
 
export default verifySignature(handler);
 
export const config = {
 api: {
 bodyParser: false,
 },
};

Hủy công việc định kỳ email đã lên lịch

Gửi thông báo qua email chính xác khi bạn muốn:Lập kế hoạch nhận biết múi giờ với QStash

Tiếp theo, hãy tạo POST /api/cancel-schedule tuyến đường. Tuyến đường này sẽ được sử dụng để hủy công việc định kỳ email đã lên lịch. Chúng tôi sẽ sử dụng thư viện QStash để hủy công việc định kỳ.

import { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";
import { Redis } from "@upstash/redis";
 
export const QSTASH_CONFIG = {
 QSTASH_URL: process.env.QSTASH_URL,
 QSTASH_TOKEN: process.env.QSTASH_TOKEN,
 QSTASH_CURRENT_SIGNING_KEY: process.env.QSTASH_CURRENT_SIGNING_KEY,
};
 
export const upstash = new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL!,
 token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
 
export default async function scheduleSummary(
 req: NextApiRequest,
 res: NextApiResponse
) {
 console.log("========REMOVE SCHEDULE SUMMARY========");
 if (req.method !== "POST") {
 return res.status(400).json({ message: "bad request" });
 }
 const { body } = req;
 
 const { userId } = body;
 
 const emailScheduleKey = `email-schedule-${userId}`;
 
 const scheduleId = await upstash.get(emailScheduleKey);
 
 // remove existing schedule before creating a new one
 if (scheduleId) {
 try {
 await axios.delete(
 `https://qstash.upstash.io/v1/schedules/${scheduleId}`,
 {
 headers: {
 Authorization: `Bearer ${QSTASH_CONFIG.QSTASH_TOKEN}`,
 },
 }
 );
 } catch (e) {
 console.log("Schedule not found in QStash ");
 }
 await upstash.del(emailScheduleKey);
 }
 
 
 return res.status(200).json({ message: "success" });
}

Kết luận

Tóm lại, chúng tôi có thể lên lịch gửi email theo múi giờ của người dùng bằng Upstash Redis và QStash. Chúng tôi đã đạt được điều này mà không cần lưu trữ múi giờ của người dùng trong cơ sở dữ liệu của mình. Chúng tôi cũng có thể cung cấp cho người dùng khả năng hủy thông báo qua email đã lên lịch bất kỳ lúc nào.

Nếu bạn đang lưu giữ tài liệu cho sản phẩm của mình, bạn nên xem tài liệu. Đó là một công cụ phản hồi được thiết kế dành cho tài liệu kỹ thuật, giúp bạn thu thập phản hồi từ người dùng và biến chúng thành thông tin chi tiết hữu ích.