Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

必要なときに電子メール アラートを配信:QStash によるタイムゾーンを意識したスケジューリング

docsly では、先週または 1 か月間に受け取ったすべてのフィードバックの概要を記載した電子メール通知をユーザーに送信する新機能を開始しました。電子メールの送信は新しい問題ではありませんが、この点で最高のユーザー エクスペリエンスを提供したいと考え、変な時間に電子メールを送信することを避けるために、すべての電子メールはユーザーのタイムゾーンで送信されるべきであると決定しました。また、ユーザーが電子メールを受信する頻度を選択できるようにしたいと考えました。また、ユーザーがいつでもスケジュールされた電子メール通知をキャンセルできるようにしたいと考えました。

このソリューションの実装は次の理由から困難でした。

<オル>
  • ユーザーのタイムゾーンをデータベースに保存したくありませんでした。
  • また、電子メールを送信する時間が来たかどうかを確認するために cron ジョブを毎分または毎時間実行することも望ましくありませんでした。
  • また、ユーザーがスケジュールされたメール通知をキャンセルできるようにしたいと考えました。
  • そこで、私たちはユニークな解決策を考え出しました。それは、ユーザーのタイムゾーンで cron ジョブをスケジュールし、ユーザーが電子メール通知をキャンセルしたときに cron ジョブをキャンセルするというものです。しかし、1 つの疑問が残りました。どうやって?

    私たちはすでに Upstash を Redis ストアとして使用していましたが、QStash がスケジュール機能もサポートしていることがわかりました。もう少し詳しく調べてみると、QStash は CRON 式もサポートしていることがわかりました。そこで、QStash を使用して cron ジョブをスケジュールすることにしました。

    この記事では、Next.js アプリケーションで QStash と Upstash Redis を使用して、ユーザーのタイムゾーンでメールをスケジュールするプロセスについて説明します。完全なソース コードは GitHub で見つけることもできます。

    ユーザーのタイムゾーンでメールをスケジュールするための Next.js アプリケーションを構築する

    前提条件

    このチュートリアルを進めるには、次のものが必要です。

    • Upstash アカウント
    • Node.js 開発環境

    プロジェクトをセットアップする

    まず、次のコマンドを使用して新しい Next.js プロジェクトを作成します。

    npx create-next-app qstash-email-scheduling

    次に、Upstash と対話するために次の依存関係をインストールします。

    npm install --save @upstash/redis axios

    新しい .env.local を作成します ファイルをプロジェクトのルートに作成し、Upstash アカウントから次の環境変数を追加します。

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

    ソリューションの概要

    コードを実装する前に、ソリューションの概要を見てみましょう。 3 つの Next.js API ルートを作成します。

    • POST /api/schedule-cron - ユーザーのタイムゾーンで電子メール cron ジョブをスケジュールします。
    • POST /api/cancel-schedule - スケジュールされた電子メール cron ジョブをキャンセルします。
    • POST /api/send-email - 電子メールを送信するためにスケジュールされた cron ジョブによってトリガーされます。

    API ルートに加えて、ユーザーのメール受信希望時刻を収集するための簡単なフォームも作成します。

    ユーザー インターフェースを作成する

    ユーザー インターフェイスを作成するには、app/page.tsx に新しいページを作成します。 次のコードを使用します:

    "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 エンドポイントを作成します。

    必要なときに電子メール アラートを配信:QStash によるタイムゾーンを意識したスケジューリング

    電子メール cron ジョブをスケジュールする

    必要なときに電子メール アラートを配信:QStash によるタイムゾーンを意識したスケジューリング

    POST /api/schedule-cron を作成することから始めましょう。 ルート。このルートは、ユーザーのタイムゾーンで電子メール 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")}`;
    }

    コードの中心は scheduleSummary です。 API エンドポイントである関数。受信した POST リクエストを処理し、次の手順を実行します。

    <オル>
  • リクエスト メソッドを検証し、POST リクエストであることを確認します。
  • リクエスト本文から userId、selectedTime、utcOffset などのデータを抽出します。
  • Upstash データベース内のユーザーのメール スケジュールのキーを構築します。
  • ユーザーの既存のスケジュールを取得し、QStash から削除します。
  • convertToUTC を使用して、ユーザーが選択した時刻を UTC 形式に変換します 時刻文字列と UTC オフセットを受け取り、オフセットを考慮しながら時刻を UTC 形式に変換する関数
  • QStash のスケジュール機能を使用して概要メールを送信するための新しいスケジュールを作成し、userId を追加します。 /api/send-email のペイロードとして エンドポイントは受信します。
  • 新しく作成されたスケジュール ID を Upstash データベースに保存します。
  • メールの概要を送信する

    POST /api/send-email ルートは、スケジュールされた cron ジョブによってトリガーされ、電子メールを送信します。 @upstash/qstash/nextjs を使用します。 ライブラリを使用してリクエストの署名を検証します。これにより、リクエストが他のソースからではなく QStash から送信されていることを確認できます。

    POST /api/send-email 関数は userId を受け取ります リクエスト本文に。 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,
     },
    };

    スケジュールされた電子メール cron ジョブをキャンセルします

    必要なときに電子メール アラートを配信:QStash によるタイムゾーンを意識したスケジューリング

    次に、POST /api/cancel-schedule を作成しましょう。 ルート。このルートは、スケジュールされた電子メール 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 を使用して、ユーザーのタイムゾーンで電子メールをスケジュールすることができました。ユーザーのタイムゾーンをデータベースに保存せずにこれを実現しました。また、スケジュールされた電子メール通知をいつでもキャンセルできる機能をユーザーに提供することもできました。

    製品のドキュメントを管理している場合は、docsly を確認してください。これは技術文書用に作成されたフィードバック ツールで、ユーザーからのフィードバックを収集し、実用的な洞察に変えるのに役立ちます。


    1. Redis ZREVRANGEBYLEX –desc値の範囲でソートされたセットの要素を取得する方法

      このチュートリアルでは、特定の範囲内で辞書式順序の降順で値を持つ並べ替えられたセット値のすべての要素を取得する方法について学習します。このために、Redis ZREVRANGEBYLEXを使用します コマンド。 ZREVRANGEBYLEXコマンド このコマンドは、指定されたキーに格納されているソートされた設定値のすべての要素を、値(要素の文字列表現)とともに返します。 最大の間 および分 引数。ここでは、辞書式順序を強制するために、ソートされた設定値のすべての要素が同じスコアで挿入されます。返される要素は、辞書式順序の降順です。 最大 および分 引数は()で始まる必要があります また

    2. go-redis、Upstash、OpenTelemetryを使用した分散トレース

      このチュートリアルでは、go-redisクライアントを使用してUpstash Redisデータベースに接続し、分散トレースを使用してアプリのパフォーマンスを監視する方法を学習します。 go-redisとは何ですか? go-redisは、Golangで人気のあるRedisクライアントです。箱から出して、Redisサーバー、Sentinel、およびクラスターをサポートします。 Upstash Redisデータベースに接続するには、次のコードを使用します。 package main import ( "context" "fmt" "