Upstash Redis এবং Workflow দিয়ে একটি স্কেলেবল নিউজলেটার অ্যাপ তৈরি করুন
এই ব্লগে, আমরা একটি নিউজলেটার অ্যাপ বানাব যেখানে ইউজাররা সাবস্ক্রাইব করতে পারবেন এবং সিলেক্ট করতে পারবেন কতক্ষণ পর পর তারা নিউজলেটার পেতে চায়। আমরা Upstash Redis ব্যবহার করব সাবস্ক্রিপশন ডেটা স্টোর করার জন্য এবং Upstash Workflow ব্যবহার করব ডেটা স্টোর করার অ্যাকশন, বেলকাম ইমেইল পাঠানো, এবং ইউজারের পছন্দ অনুযায়ী নিউজলেটার শিডিউল করার জন্য।
উদ্দেশ্য
প্রথমেই, সার্ভারлес এনভায়রনমেন্ট গরেট! তারা হাইলি স্কেলেবল এবং বাজেট-ফ্রেন্ডলি। যাইহোক, তাদের কিছু লিমিটেশন থাকে, যেমন এক্সিকিউশন টাইম লিমিট। এটি বিশেষভাবে সমস্যা সৃষ্টি করে যখন আপনাকে লং-রানিং টাস্ক রান করতে হয়।
এখানেই Upstash Workflow কাজে আসে। Upstash Workflow-র সাথে, আপনি পারসিস্টেন্ট ওয়ার্কফ্লো তৈরি করতে পারেন যেগুলো যতক্ষণ দরকার ততক্ষণ রান করতে পারে। তাই, আপনাকে আর সার্ভারлес ফাংশন টাইমআউট নিয়ে চিন্তা করতে হবে না।
Upstash Workflow ব্যবহার করার সময় আপনি যা পাবেন তার একটি তালিকা:
সার্ভারлес ফাংশন টাইমআউট আর নেই: আপনার ওয়ার্কফ্লো যতক্ষণ দরকার ততক্ষণ রান করতে পারে।
অটোমেটিক রিকভারি: যদি কিছু ভুল হয় এবং ওয়ার্কফ্লো মাঝপথে ফেইল করে, এটি অটোমেটিক্যালি রিকভার হয়।
অটোমেটিক রিট্রাই: যদি ওয়ার্কফ্লোর যেকোনো স্টেপ ফেইল করে, এটি অটোমেটিক্যালি রিট্রাই হয়।
রিয়েল-টাইম মনিটরিং: আপনি Upstash কনসোল থেকে আপনার ওয়ার্কফ্লো রিয়েল-টাইমে মনিটর করতে পারেন।
প্রত্যোজনীয় শর্তাবলী
Next.js অ্যাপ্লিকেশনের বেসিক বোঝাপড়া।
Redis এবং QStash টোকেনের জন্য একটি Upstash অ্যাকাউন্ট।
ডিপ্লয়মেন্টের জন্য Vercel অ্যাকাউন্ট।
লোকাল ডেভেলপমেন্টের জন্য ngrok (সুপারিশ করা)।
প্রজেক্ট সেটআপ
চলুন create-next-app ব্যবহার করে একটি নতুন Next.js প্রজেক্ট বুটস্ট্র্যাপ করি:
npx create-next-app@latest --typescript newsletter-app cd newsletter-app
এখন, Upstash QStash এবং Redis সার্ভিসের সাথে ইন্টারঅ্যাক্ট করার জন্য প্রয়োজনীয় ডিপেন্ডেন্সি যোগ করি:
npm install @upstash/qstash @upstash/redis
ডিরেক্টরি স্ট্রাকচার
কোডে ঝাঁপড়াওড়ার আগে, আসুন দ্রুত দেখি আমরা কীভাবে আমাদের প্রজেক্ট অর্গানাইজ করব:
src/app/: এখানে আমাদের মূল অ্যাপ্লিকেশন কম্পোনেন্ট এবং পেজ থাকবে।
src/app/api/: আমরা এখানে আমাদের API রুট রাখব—সাবস্ক্রাইব করার জন্য, আনসাবস্ক্রাইব করার জন্য, এবং ওয়ার্কফ্লো হ্যান্ডেল করার জন্য।
src/components/: এই ফোল্ডারে আমাদের সাবস্ক্রিপশন এবং আনসাবস্ক্রিপশন ফর্ম কম্পোনেন্ট থাকবে।
src/lib/: Redis এবং ইমেইল পাঠানোর ইউটিলিটি ফাংশনগুলো এখানে থাকবে।
src/types/: আমরা আমাদের TypeScript টাইপ ডেফিনিশন এই ডিরেক্টরিতে রাখব।
এনভায়রনমেন্ট ভেরিয়েবল
আমাদের প্রজেক্টের রুটে একটি .env ফাইল তৈরি করতে হবে এবং নিচের موارد যোগ করতে হবে:
QSTASH_TOKEN: আমাদের Upstash QStash টোকেন Upstash কনসোল থেকে অ্যাক্সেস করা।
UPSTASH_REDIS_REST_URL এবং UPSTASH_REDIS_REST_TOKEN: আমাদের Upstash Redis ক্রেডেনশিয়াল Upstash কনসোল থেকে অ্যাক্সেস করা।
EMAIL_SERVICE_URL: আমাদের ইমেইল পাঠানোর API-এর এন্ডপয়েন্ট।
NEXT_PUBLIC_BASE_URL: আমাদের ডিপ্লয়েড অ্যাপ্লিকেশনের বেস URL (যেমন, https://your-app.vercel.app)।
আমরা লোকাল ডেভেলপমেন্টের জন্য আমাদের ngrok URL দিয়ে UPSTASH_WORKFLOW_URL ভেরিয়েবলও আমাদের .env ফাইলে সেট করতে পারি। ngrok দিয়ে লোকালি ওয়ার্কফ্লো ডেভেলপ করার বিষয়ে জানতে Upstash ডকুমেন্টেশন রেফার করুন।
UPSTASH_WORKFLOW_URL এনভায়রনমেন্ট ভেরিয়েবল শুধু লোকাল ডেভেলপমেন্টের জন্য প্রয়োজনীয়। প্রোডাকশনে, baseUrl প্যারামিটার অটোমেটিক্যালি সেট হয় এবং অমিট করা যাবে।
প্রজেক্ট ইমপ্লিমেন্টেশন
সাবস্ক্রিপশন ফর্ম কম্পোনেন্ট
SubscriptionForm কম্পোনেন্টটি ইউজারদের তাদের ইমেইল দিতে এবং সিলেক্ট করতে দেয় কতক্ষণ পর পর তারা নিউজলেটার পেতে চায়। ফর্ম সাবমিট হলে, আমরা ফর্ম ডেটাসহ /api/subscribe-এ একটি POST রিকোয়েস্ট পাঠাই।
UnsubscribeForm কম্পোনেন্টটি ইউজারদের তাদের ইমেইল দিয়ে নিউজলেটার থেকে আনসাবস্ক্রাইব করতে দেয়। ফর্ম সাবমিট হলে, আমরা ইমেইল ডেটাসহ /api/unsubscribe-এ একটি POST রিকোয়েস্ট পাঠাই। এটি ইমেইল ফিল্ড প্রি-ফিল করে যদি ইউজার ইমেইলগুলোর একটি আনসাবস্ক্রাইব লিংকতে ক্লিক করে।
আমরা Upstash Redis ব্যবহার করব ইউজার সাবস্ক্রিপশন ডেটা স্টোর করার জন্য।
Upstash Redis ব্যবহার করার জন্য, আমাদের প্রথমে Upstash কনসোলে একটি Redis ডেটাবেস সেট আপ করতে হবে এবং আমাদের REST URL এবং টোকেন নিতে হবে। আরও তথ্যের জন্য, Upstash ডকুমেন্টেশন চেক আউট করতে পারেন।
redis.ts আমাদের Redis ক্লায়েন্ট এবং Redis-এ ইন্টারঅ্যাক্ট করার হেল্পার ফাংশন থাকবে:
src/lib/redis.ts
import { Redis } from "@upstash/redis"; export const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); export async function getUserFrequency(email: string): Promise { const data = await redis.get(`user:${email}`); console.log("User data:", data); if (!data) return null; const parsed = JSON.parse(JSON.stringify(data)); return parsed.frequency; } export async function removeUser(email: string): Promise { await redis.del(`user:${email}`); } export async function checkSubscription(email: string): Promise { return (await getUserFrequency(email)) !== null; }
ইমেইল পাঠানোর ফাংশন
ইমেইল পাঠানোর জন্য, আমরা আমাদের নিজস্ব ইমেইল API ব্যবহার করব যা আমরা QStash Python SDK দিয়ে Email Scheduler তৈরি করার পূর্বের ব্লগ পোস্টে ডেভেলপ করেছিলাম।
src/lib/email.ts
export async function sendEmail(message: string, email: string) { console.log(`Sending email to ${email}`); const url = process.env.EMAIL_SERVICE_URL; const payload = { to_email: email, subject: "Upstash Newsletter", content: message, }; if (!url) { console.error("EMAIL_SERVICE_URL is not defined."); return; } const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); if (!response.ok) { console.error("Failed to send email:", await response.text()); } }
টাইপ ডেফিনিশন
আমাদের সাবস্ক্রিপশন ডেটার জন্য একটি টাইপ ডেফিনিশনও দরকার:
আমরা একটি API রুট তৈরি করব যা সাবস্ক্রিপশন রিকোয়েস্ট হ্যান্ডেল করবে। যখন একজন ইউজার সাবস্ক্রিপশন ফর্ম সাবমিট করবে, এই এন্ডপয়েন্টটি চেক করবে ইউজার ইতিমধ্যে সাবস্ক্রিব্ড কিনা এবং একটি ওয়ার্কফ্লো এনকিউ করবে ইউজারের বেছে নেওয়া ফ্রিকোয়েন্সি অনুযায়ী ইমেইল পাঠানোর জন্য।
src/app/api/subscribe/route.ts
import { NextRequest, NextResponse } from "next/server"; import { checkSubscription } from "@/lib/redis"; export const POST = async (request: NextRequest) => { try { const { email, frequency: freq, customFrequency } = await request.json(); console.log("Email:", email); console.log("Frequency:", freq); console.log("Custom Frequency:", customFrequency); if (!email || !freq) { console.error("Email and frequency are required."); return NextResponse.json( { error: "Email and frequency are required." }, { status: 400 } ); } let frequency = freq; if (frequency === "custom") { if (!customFrequency) { console.error("Custom frequency days are required."); return NextResponse.json( { error: "Custom frequency days are required." }, { status: 400 } ); } frequency = customFrequency; } if (frequency === "daily") { frequency = "1"; } else if (frequency === "weekly") { frequency = "7"; } else if (frequency === "monthly") { frequency = "30"; } const frequencyNumber = Number(frequency); if (isNaN(frequencyNumber) || frequencyNumber <= 0) { console.error("Invalid frequency value."); return NextResponse.json( { error: "Invalid frequency value." }, { status: 400 } ); } const exists = await checkSubscription(email); if (exists) { console.error("Email is already subscribed."); return NextResponse.json( { error: "Email is already subscribed." }, { status: 400 } ); } console.log("Subscription successful!"); console.log("Enqueue the workflow"); // Enqueue the workflow await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/workflow`, { method: "POST", headers: { Authorization: `Bearer ${process.env.QSTASH_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ email: email, frequency: frequencyNumber, }), }) .then((response) => { if (!response.ok) { console.error("Failed to enqueue workflow:", response.statusText); return NextResponse.json( { error: "Failed to enqueue workflow." }, { status: 500 } ); } else { console.log("Workflow enqueued successfully"); } }) .catch((error) => { console.error("Error enqueuing workflow:", error); return NextResponse.json( { error: "Error enqueuing workflow." }, { status: 500 } ); }); return NextResponse.json({ message: "Subscription successful!" }); } catch (error) { console.error("Error occurred:", error); return NextResponse.json( { error: "An error occurred during subscription." }, { status: 500 } ); } };
Unsubscribe API রুট
যেহেতু আমাদের একটি সাবস্ক্রিপশন রুট আছে, আমাদের একটি আনসাবস্ক্রিপশন রুটও দরকার। যখন একটি রিকোয়েস্ট আসবে, আমরা চেক করব ইউজার সাবস্ক্রিব্ড কিনা এবং Redis থেকে তাদের ডেটা রিমুভ করব। আমরা একটি কনফার্মেশন ইমেইলও পাঠাব।
src/app/api/unsubscribe/route.ts
import { NextRequest, NextResponse } from "next/server"; import { redis } from "@/lib/redis"; import { sendEmail } from "@/lib/email"; export const POST = async (request: NextRequest) => { try { const { email } = await request.json(); if (!email) { return NextResponse.json( { error: "Email is required." }, { status: 400 } ); } const userExists = await redis.exists(`user:${email}`); if (!userExists) { return NextResponse.json( { error: "Email is not subscribed." }, { status: 400 } ); } // Remove the user from Redis await redis.del(`user:${email}`); // Send an email to confirm unsubscription await sendEmail( "You have been unsubscribed from Upstash Newsletter.", email ); return NextResponse.json({ message: "You have been unsubscribed." }); } catch (error) { console.error("Unsubscribe error:", error); return NextResponse.json( { error: "An error occurred. Please try again." }, { status: 500 } ); } };
Workflow API রুট
এখন, মজার অংশ! আমরা একটি API রুট তৈরি করব যা নির্দিষ্ট ফ্রিকোয়েন্সি ইন্টারভালে নিউজলেটার পাঠানোর ওয়ার্কফ্লো হ্যান্ডেল করবে।
আমাদের ওয়ার্কফ্লো নিচের কাজগুলো করবে:
ইউজারের সাবস্ক্রিপশন ডেটা Redis-এ স্টোর করা।
একটি বেলকাম ইমেইল পাঠানো।
একটি লুপে প্রবেশ:
নির্দিষ্ট ফ্রিকোয়েন্সি ডুরেশন পর্যন্ত অপেক্ষা করা।
চেক করা ইউজার এখনো সাবস্ক্রিব্ড কিনা।
নিউজলেটার ইমেইল পাঠানো।
রিপিট করা যতক্ষণ না একটি সেট সংখ্যক নিউজলেটার পাঠানো হয় কারণ আমরা অনন্ত লুপ চাই না।
src/app/api/workflow/route.ts
এখানে একটি সম্পূর্ণ ওয়ার্কফ্লোর উদাহরণ দেওয়া হলো একজন ইউজারের জন্য যিনি সাবস্ক্রাইব করেছিলেন, একটি নিউজলেটার পেয়েছিলেন, এবং আনসাবস্ক্রাইব করেছিলেন:
আপনি Upstash কনসোল থেকে আপনার ওয়ার্কফ্লো অ্যাক্সেস এবং মনিটর করতে পারেন।
মেইন পেজ কম্পোনেন্ট
চলুন আমাদের অ্যাপ্লিকেশনের মেইন পেজ সেট আপ করি। এই পেজে সাবস্ক্রিপশন ফর্ম এবং আনসাবস্ক্রাইব পেজের লিংক থাকবে।
src/app/page.tsx
import SubscriptionForm from "@/components/SubscriptionForm"; import Link from "next/link"; export default function Home() { return (
Subscribe to Upstash Newsletter
{/* Subscription Form */} {/* Unsubscribe Link */}
Already subscribed and want to unsubscribe? Click here to unsubscribe
); }
আনসাবস্ক্রাইব পেজ কম্পোনেন্ট
শেষে, আনসাবস্ক্রিপশন পেজ তৈরি করি।
src/app/unsubscribe/page.tsx
import UnsubscribePage from "@/components/UnsubscribeForm"; export default function UnsubscribeHome() { return (
Unsubscribe from Upstash Newsletter
{/* Unsubscribe Form */} ); }
উপসংহার
হয়ে গেলো! আমরা সার্ভারлес ফাংশন টাইমআউট নিয়ে চিন্তা না করে একটি সিম্পল নিউজলেটার অ্যাপ বানিয়েছি।
আপনি এই প্রজেক্টের সম্পূর্ণ সোর্স কোড GitHub-এ পাবেন এবং লাইভ ডেমো এখানে চেক আউট করতে পারেন।
Upstash Workflow-র আরও তথ্যের জন্য, Upstash ডকুমেন্টেশন রেফার করুন।
যদি আপনার কোনো প্রশ্ন থাকে, আমাদের সাথে Discord-এ যোগাযোগ করতে সংকোচ করবেন না। এছাড়া, Upstash ব্লগ এক্সপ্লোর করতে ভুলবেন না আরও টিউটোরিয়াল এবং ইউজ-কেসের জন্য।