ที่ Docsly เราได้เปิดตัวฟีเจอร์ใหม่เพื่อส่งการแจ้งเตือนทางอีเมลไปยังผู้ใช้พร้อมสรุปความคิดเห็นทั้งหมดที่พวกเขาได้รับในสัปดาห์หรือเดือนที่ผ่านมา การส่งอีเมลไม่ใช่ปัญหาใหม่ แต่เราต้องการมอบประสบการณ์ผู้ใช้ที่ดีที่สุดในเรื่องนี้ ดังนั้นเราจึงตัดสินใจว่าควรส่งอีเมลทั้งหมดในเขตเวลาของผู้ใช้เพื่อหลีกเลี่ยงการส่งอีเมลในเวลาไม่ปกติ นอกจากนี้เรายังต้องการให้ผู้ใช้สามารถเลือกความถี่ที่ต้องการรับอีเมลได้ นอกจากนี้เรายังต้องการให้ผู้ใช้สามารถยกเลิกการแจ้งเตือนทางอีเมลที่กำหนดเวลาไว้ได้ตลอดเวลา
การใช้โซลูชันนี้เป็นเรื่องยากเนื่องจาก:
- เราไม่ต้องการจัดเก็บเขตเวลาของผู้ใช้ในฐานข้อมูลของเรา
- เรายังไม่ต้องการรันงาน cron ทุกนาทีหรือชั่วโมงเพื่อตรวจสอบว่าถึงเวลาส่งอีเมลหรือไม่
- เรายังต้องการให้ผู้ใช้ยกเลิกการแจ้งเตือนทางอีเมลที่กำหนดเวลาไว้
ดังนั้นเราจึงคิดวิธีแก้ปัญหาที่ไม่เหมือนใคร:กำหนดเวลางาน cron ในเขตเวลาของผู้ใช้ จากนั้นยกเลิกงาน cron เมื่อผู้ใช้ยกเลิกการแจ้งเตือนทางอีเมล แต่คำถามหนึ่งยังคงอยู่:อย่างไร?
เราใช้ Upstash เป็นร้านค้า Redis อยู่แล้ว และเราพบว่า QStash รองรับฟีเจอร์ Schedules เช่นกัน เมื่อเจาะลึกลงไปอีกเล็กน้อย เราพบว่า QStash รองรับนิพจน์ CRON เช่นกัน ดังนั้นเราจึงตัดสินใจใช้ QStash เพื่อกำหนดเวลางาน cron
ในบทความนี้ เราจะแนะนำคุณตลอดกระบวนการกำหนดเวลาอีเมลในเขตเวลาของผู้ใช้โดยใช้ QStash และ Upstash Redis ในแอปพลิเคชัน Next.js คุณยังค้นหาซอร์สโค้ดฉบับสมบูรณ์ได้ที่ GitHub
การสร้างแอปพลิเคชัน Next.js เพื่อกำหนดเวลาอีเมลในเขตเวลาของผู้ใช้
ข้อกำหนดเบื้องต้น
หากต้องการติดตามบทช่วยสอนนี้ คุณจะต้อง:
- บัญชี Upstash
- สภาพแวดล้อมการพัฒนา Node.js
ตั้งค่าโครงการ
ในการเริ่มต้น ให้สร้างโปรเจ็กต์ Next.js ใหม่โดยใช้คำสั่งต่อไปนี้:
npx create-next-app qstash-email-scheduling จากนั้น ติดตั้งการขึ้นต่อกันต่อไปนี้เพื่อโต้ตอบกับ Upstash:
npm install --save @upstash/redis axios
สร้าง 06 ใหม่ ในรากของโครงการของคุณและเพิ่มตัวแปรสภาพแวดล้อมต่อไปนี้จากบัญชี Upstash ของคุณ:
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
QSTASH_URL=
QSTASH_TOKEN=
QSATSH_CURRENT_SIGNING_KEY=
QSATSH_NEXT_SIGNING_KEY= ภาพรวมโซลูชัน
ก่อนที่เราจะติดตั้งโค้ด เรามาดูภาพรวมของโซลูชันกันก่อน เราจะสร้างเส้นทาง Next.js API สามเส้นทาง:
16รหัส> - เพื่อกำหนดเวลางาน cron อีเมลในเขตเวลาของผู้ใช้23รหัส> - เพื่อยกเลิกงาน cron อีเมลที่กำหนดเวลาไว้31รหัส> - ทริกเกอร์โดยงาน cron ที่กำหนดเวลาไว้เพื่อส่งอีเมล
นอกจากเส้นทาง API แล้ว เรายังสร้างแบบฟอร์มง่ายๆ เพื่อรวบรวมเวลาที่ต้องการรับอีเมลของผู้ใช้
สร้างอินเทอร์เฟซผู้ใช้
ในการสร้างส่วนติดต่อผู้ใช้ เราจะสร้างหน้าใหม่ที่ 46 ด้วยรหัสต่อไปนี้:
"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>
);
} โค้ดด้านบนจะสร้างแบบฟอร์มพร้อมเมนูแบบเลื่อนลงเพื่อให้ผู้ใช้ตั้งเวลาที่ต้องการรับอีเมลสรุปรายวัน พวกเขาสามารถกำหนดเวลาหรือยกเลิกการแจ้งเตือนเหล่านี้ได้ และแอปพลิเคชันจะสื่อสารกับเซิร์ฟเวอร์โดยใช้คำขอ HTTP POST เราจะสร้างปลายทาง HTTP ในส่วนต่อไปนี้

กำหนดเวลางาน cron อีเมล

เริ่มต้นด้วยการสร้าง 50 เส้นทาง เส้นทางนี้จะใช้เพื่อกำหนดเวลางาน cron อีเมลในเขตเวลาของผู้ใช้ เราจะใช้ไลบรารี QStash เพื่อกำหนดเวลางาน cron
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")}`;
}
แกนหลักของโค้ดคือ 62 ฟังก์ชันซึ่งเป็นจุดสิ้นสุด API โดยจะจัดการกับคำขอ POST ที่เข้ามาและดำเนินการตามขั้นตอนต่อไปนี้:
- ตรวจสอบวิธีการร้องขอ เพื่อให้แน่ใจว่าเป็นคำขอ POST
- ดึงข้อมูลจากเนื้อหาคำขอ รวมถึง userId, SelectedTime และ utcOffset
- สร้างคีย์สำหรับกำหนดเวลาอีเมลของผู้ใช้ในฐานข้อมูล Upstash
- ดึงข้อมูลกำหนดการที่มีอยู่สำหรับผู้ใช้และลบออกจาก QStash
- แปลงเวลาที่ผู้ใช้เลือกเป็นรูปแบบ UTC โดยใช้
75ฟังก์ชันที่รับสตริงเวลาและออฟเซ็ต UTC โดยแปลงเวลาเป็นรูปแบบ UTC ในขณะที่คำนึงถึงออฟเซ็ต - สร้างกำหนดการใหม่สำหรับการส่งอีเมลสรุปโดยใช้ฟังก์ชันการกำหนดเวลาของ QStash และเพิ่ม
82เป็นเพย์โหลดที่95ปลายทางได้รับ - จัดเก็บ ID กำหนดการที่สร้างขึ้นใหม่ในฐานข้อมูล Upstash
ส่งอีเมลสรุป
102รหัส> เส้นทางจะถูกทริกเกอร์โดยงาน cron ที่กำหนดเวลาไว้เพื่อส่งอีเมล เราจะใช้ 113 ห้องสมุดเพื่อตรวจสอบลายเซ็นของคำขอ สิ่งนี้จะช่วยให้แน่ใจว่าคำขอนั้นมาจาก QStash และไม่ได้มาจากแหล่งอื่นใด
128รหัส> ฟังก์ชั่นรับ 136 ในเนื้อหาคำขอ คุณสามารถใช้ฟังก์ชันเพื่อจัดเตรียมและส่งอีเมลตาม 145 .
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,
},
}; ยกเลิกงาน cron อีเมลที่กำหนดเวลาไว้

ต่อไปเรามาสร้าง 154 เส้นทาง เส้นทางนี้จะใช้เพื่อยกเลิกงาน cron อีเมลที่กำหนดเวลาไว้ เราจะใช้ไลบรารี QStash เพื่อยกเลิกงาน cron
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" });
} บทสรุป
โดยสรุป เราสามารถกำหนดเวลาอีเมลในเขตเวลาของผู้ใช้ได้โดยใช้ Upstash Redis และ QStash เราบรรลุเป้าหมายนี้โดยไม่ต้องจัดเก็บเขตเวลาของผู้ใช้ไว้ในฐานข้อมูลของเรา นอกจากนี้เรายังสามารถให้ผู้ใช้สามารถยกเลิกการแจ้งเตือนทางอีเมลที่กำหนดเวลาไว้ได้ตลอดเวลา
หากคุณกำลังเก็บเอกสารสำหรับผลิตภัณฑ์ของคุณ คุณควรตรวจสอบเอกสาร เป็นเครื่องมือคำติชมที่สร้างขึ้นสำหรับเอกสารทางเทคนิคที่ช่วยให้คุณรวบรวมคำติชมจากผู้ใช้และเปลี่ยนให้เป็นข้อมูลเชิงลึกที่นำไปใช้ได้จริง