लारवेल के साथ ढेर सारे ओपन-सोर्स इनवॉइस मैनेजमेंट ऐप बनाए गए हैं। एक जावास्क्रिप्ट डेवलपर के रूप में, मैं उन देवों के लिए "रिएक्ट सॉल्यूशन" बनाना चाहता था जो रिएक्ट और जावास्क्रिप्ट से परिचित हों।
Node.js में सेवाओं के साथ निर्माण करते समय मुझे एक समस्या यह मिली कि कोई अंतर्निहित मेलर नहीं है। इसलिए, मेरे लिए ऐसा करने के लिए मुझे एक तृतीय पक्ष सेवा ढूंढनी पड़ी। इस लेख में, मैं इस परियोजना के लिए ईमेल भेजने के लिए कूरियर को एकीकृत करूंगा https://github.com/fazzaamiarso/invoys ।
चूंकि यह लेख आपका विशिष्ट फॉलो-अलॉन्ग नहीं है (अधिक जैसे "कृपया कसकर बैठें और देखें कि मैं इसे कैसे करता हूं"), उपयोग की जाने वाली सभी तकनीकों से परिचित होना अनिवार्य नहीं है। हालाँकि, टाइपस्क्रिप्ट और नेक्स्ट.जेएस के साथ परिचित होना त्वरित समझ के लिए फायदेमंद होगा।
इस ब्लॉग में प्रौद्योगिकियां:
टाइपस्क्रिप्ट : टाइप-सेफ्टी और ऑटो-कंप्लीशन सबसे अच्छे हैं, है ना?
Next.js : फुल-स्टैक ऐप बनाने के लिए एक प्रोडक्शन-रेडी फ्रेमवर्क, शुरुआती लोगों के लिए भी।
प्रिज्मा : डेटाबेस के साथ काम करने के लिए एक बेहतरीन ओआरएम। हम प्रिज्मा का उपयोग इसकी टाइप-सेफ्टी और ऑटो-कम्पलीशन के कारण करते हैं, जो टाइपस्क्रिप्ट के साथ शानदार डेवलपर अनुभव प्रदान करते हैं।
Trpc : हमें अपने Next.js क्लाइंट और सर्वर के बीच एंड-टू-एंड टाइप-सेफ्टी आसानी से बनाने में सक्षम बनाता है।
कूरियर एपीआई: हमारी सूचनाओं को संभालने के लिए एक महान सेवा/प्लेटफॉर्म, जैसे ईमेल, एसएमएस और बहुत कुछ।
आप संदर्भ के लिए यहां पूर्ण स्रोत कोड पा सकते हैं।
सुविधाओं का निर्माण करने से पहले, आइए अपने लक्ष्यों को परिभाषित करें।
चलिए कूरियर डैशबोर्ड पर चलते हैं। डिफ़ॉल्ट रूप से, यह उत्पादन वातावरण में है। चूंकि मैं चीजों का परीक्षण करना चाहता हूं, मैं शीर्ष-दाएं कोने में ड्रॉपडाउन पर क्लिक करके परीक्षण वातावरण में बदलाव करने जा रहा हूं।
हम सभी टेम्प्लेट को बाद में उत्पादन या इसके विपरीत कॉपी कर सकते हैं।
अब, मैं अपनी ईमेल सूचनाओं के लिए एक ब्रांड बनाऊँगा।
मैं केवल एक लोगो जोड़ने जा रहा हूं (सावधान रहें कि लोगो की चौड़ाई 140px तय की गई है) और पाद लेख पर सामाजिक लिंक। डिज़ाइनर UI बहुत सीधा है, इसलिए यहाँ अंतिम परिणाम है।
परिवर्तनों को प्रकाशित करना न भूलें।
वर्तमान में, UI पर ईमेल भेजें बटन कुछ नहीं कर रहा है।
मैं कूरियर से संबंधित सभी कोड रखने के लिए src/lib/
में एक courier.ts
फ़ाइल बनाने जा रहा हूं। इसके अलावा, मैं कूरियर नोड.जेएस क्लाइंट लाइब्रेरी का उपयोग करूंगा जो पहले से ही कार्यों के लिए सभी कूरियर एपीआई एंडपॉइंट्स को सारणित करता है।
इससे पहले कि मैं कार्यात्मकता का निर्माण करूँ, चलिए कूरियर के डिज़ाइनर के भीतर ईमेल सूचना डिज़ाइन बनाते हैं और Gmail प्रदाता सेट करते हैं।
ईमेल डिज़ाइनर पेज पर, हम देखेंगे कि बनाया गया ब्रांड पहले से ही एकीकृत है। उसके बाद, आवश्यक डेटा के अनुसार टेम्पलेट को डिज़ाइन करते हैं। यहाँ अंतिम परिणाम है।
ध्यान दें कि {}
वाला मान हरा हो जाता है, इसका मतलब है कि यह एक चर है जिसे गतिशील रूप से डाला जा सकता है। मैं एक चर के साथ 'चालान देखें' बटन (या क्रिया) भी सेट करता हूं।
इससे पहले कि मैं टेम्पलेट का उपयोग कर सकूँ, मुझे पूर्वावलोकन टैब पर क्लिक करके एक परीक्षण ईवेंट बनाना होगा। फिर, यह घटना को नाम देने और JSON प्रारूप में data
सेट करने का संकेत दिखाएगा। वह डेटा फ़ील्ड वह है जो हरे {}
चर के मान को पॉप्युलेट करेगा (डेटा को कोड से भी सेट किया जा सकता है)। चूंकि यह एक परीक्षण घटना है, मैं इसे मनमाने मूल्यों से भर दूंगा।
अगला, मैं टेम्पलेट प्रकाशित करूँगा ताकि मैं इसका उपयोग कर सकूँ। इसके बाद सेंड टैब पर जाएं। यह ईमेल को प्रोग्रामेटिक रूप से भेजने के लिए आवश्यक कोड दिखाएगा और data
मेरे द्वारा बनाए गए पिछले परीक्षण कार्यक्रम से भर जाएगा।
मैं परीक्षण AUTH_TOKEN
.env
फ़ाइल में कॉपी करूँगा और स्निपेट को src/lib/courier.ts
पर कॉपी करूँगा।
const authToken = process.env.COURIER_AUTH_TOKEN; // email to receive all sent notifications in DEVELOPMENT mode const testEmail = process.env.COURIER_TEST_EMAIL; const INVOICE_TEMPLATE_ID = <TEMPLATE_ID>; const courierClient = CourierClient({ authorizationToken: authToken, });
एक sendInvoice
फ़ंक्शन बनाएँ जो एक ईमेल भेजने के लिए ज़िम्मेदार होगा। कोड से एक ईमेल भेजने के लिए, मैं courierClient.send()
फ़ंक्शन का उपयोग करता हूं।
// src/lib/courier.ts export const sendInvoice = async ({ customerName, invoiceNumber, invoiceViewUrl, emailTo, productName, dueDate, }: SendInvoice) => { const recipientEmail = process.env.NODE_ENV === "production" ? emailTo : testEmail; const { requestId } = await courierClient.send({ message: { to: { email: recipientEmail, }, template: INVOICE_TEMPLATE_ID, // Data for courier template designer data: { customerName, invoiceNumber, invoiceViewUrl, productName, dueDate, }, }, }); return requestId };
sendInvoice
फ़ंक्शन के प्रकार परिभाषित करें।
// src/lib/courier.ts interface SendInvoice { productName: string; dueDate: string; customerName: string; invoiceNumber: string; invoiceViewUrl: string; emailTo: string; }
अब जब मैं ईमेल भेज सकता हूं, तो मैं इसे src/server/trpc/router/invoice.ts
में स्थित sendEmail
trpc एंडपॉइंट में कॉल करूंगा।
बस याद रखें कि trpc एंडपॉइंट एक Next.js API रूट है। इस मामले में,
sendEmail
हुड के नीचेfetch
के साथ/api/trpc/sendEmail
रूट को कॉल करने जैसा ही होगा । अधिक स्पष्टीकरण के लिए https://trpc.io/docs/quickstart ।
// src/server/trpc/router/invoice.ts import { sendInvoice } from '@lib/courier'; import { dayjs } from '@lib/dayjs'; // .....SOMEWHERE BELOW sendEmail: protectedProcedure .input( z.object({ customerName: z.string(), invoiceNumber: z.string(), invoiceViewUrl: z.string(), emailTo: z.string(), invoiceId: z.string(), productName: z.string(), dueDate: z.date(), }) ) .mutation(async ({ input }) => { const invoiceData = { ...input, dueDate: dayjs(input.dueDate).format('D MMMM YYYY'), }; await sendInvoice(invoiceData); }),
उन लोगों के लिए जो टीआरपीसी से अपरिचित हैं, मैंने जो किया वह POST
अनुरोध को संभालने जैसा ही है। आइए इसे तोड़ दें।
Zod के साथ सत्यापन करके क्लाइंट से अनुरोध इनपुट को परिभाषित करने का Trpc तरीका। यहां मैं उन सभी डेटा को परिभाषित करता हूं जो कि sendInvoice
फ़ंक्शन के लिए आवश्यक हैं।
.input( z.object({ customerName: z.string(), invoiceNumber: z.string(), invoiceViewUrl: z.string(), emailTo: z.string(), invoiceId: z.string(), productName: z.string(), dueDate: z.date(), }) )
POST
अनुरोध हैंडलर (म्यूटेशन) को परिभाषित करें। // input from before .mutation(async ({ input }) => { const invoiceData = { ...input, // format a date to string with a defined format. dueDate: dayjs(input.dueDate).format('D MMMM YYYY'), // ex.'2 January 2023' }; // send the email await sendInvoice(invoiceData); }),
अब, मैं सेंड ईमेल बटन में कार्यक्षमता जोड़ना शुरू कर सकता हूं। मैं trpc.useMutation()
फ़ंक्शन का उपयोग करने जा रहा हूं जो tanstack-query's
उपयोग म्यूटेशन का एक पतला आवरण है।
आइए म्यूटेशन फ़ंक्शन जोड़ें। सफल प्रतिक्रिया पर, मैं यूआई पर एक सफल टोस्ट भेजना चाहता हूं।
//src/pages/invoices/[invoiceId]/index.tsx import toast from 'react-hot-toast'; const InvoiceDetail: NextPage = () => { // calling the `sendEmail` trpc endpoint with tanstack-query. const sendEmailMutation = trpc.invoice.sendEmail.useMutation({ onSuccess() { toast.success('Email sent!'); } }); }
मैं फ़ंक्शन को इनलाइन हैंडलर के रूप में उपयोग कर सकता हूं, लेकिन मैं बटन के लिए एक नया हैंडलर बनाना चाहता हूं।
//src/pages/invoices/[invoiceId]/index.tsx // still inside the InvoiceDetail component const sendInvoiceEmail = () => { const hostUrl = window.location.origin; // prevent a user from spamming when the API call is not done. if (sendEmailMutation.isLoading) return; // send input data to `sendEmail` trpc endpoint sendEmailMutation.mutate({ customerName: invoiceDetail.customer.name, invoiceNumber: `#${invoiceDetail.invoiceNumber}`, invoiceViewUrl: `${hostUrl}/invoices/${invoiceDetail.id}/preview`, emailTo: invoiceDetail.customer.email, invoiceId: invoiceDetail.id, dueDate: invoiceDetail.dueDate, productName: invoiceDetail.name, }); };
अब मैं हैंडलर को सेंड ईमेल बटन से अटैच कर सकता हूं।
//src/pages/invoices/[invoiceId]/index.tsx <Button variant="primary" onClick={sendInvoiceEmail} isLoading={sendEmailMutation.isLoading}> Send to Email </Button>
यहाँ कार्यशील UI है।
चालान की नियत तारीख से एक दिन पहले भेजे जाने वाले रिमाइंडर को शेड्यूल करने के लिए, मैं कूरियर के ऑटोमेशन एपीआई का उपयोग करने जा रहा हूं।
सबसे पहले, चलिए कूरियर डिज़ाइनर में ईमेल टेम्प्लेट डिज़ाइन करते हैं। जैसा कि मैं पहले ही प्रक्रिया से गुजर चुका हूं, यहां अंतिम परिणाम है।
फ़ंक्शन जोड़ने से पहले, पैरामीटर के प्रकारों को परिभाषित करें और प्रकारों को रिफैक्टर करें।
// src/lib/courier interface CourierBaseData { customerName: string; invoiceNumber: string; invoiceViewUrl: string; emailTo: string; } interface SendInvoice extends CourierBaseData { productName: string; dueDate: string; } interface ScheduleReminder extends CourierBaseData { scheduledDate: Date; invoiceId: string; }
अब, मैं src/lib/courier
में scheduleReminder
फ़ंक्शन जोड़ता हूं
//src/pages/invoices/[invoiceId]/index.tsx // check if the development environment is production const __IS_PROD__ = process.env.NODE_ENV === 'production'; const PAYMENT_REMINDER_TEMPLATE_ID = '<TEMPLATE_ID>'; export const scheduleReminder = async ({ scheduledDate, emailTo, invoiceViewUrl, invoiceId, customerName, invoiceNumber, }: ScheduleReminder) => { // delay until a day before due date in production, else 20 seconds after sent for development const delayUntilDate = __IS_PROD__ ? scheduledDate : new Date(Date.now() + SECOND_TO_MS * 20); const recipientEmail = __IS_PROD__ ? emailTo : testEmail; // define the automation steps programmatically const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { steps: [ // 1. Set delay for the next steps until given date in ISO string { action: 'delay', until: delayUntilDate.toISOString() }, // 2. Send the email notification. Equivalent to `courierClient.send()` { action: 'send', message: { to: { email: recipientEmail }, template: PAYMENT_REMINDER_TEMPLATE_ID, data: { invoiceViewUrl, customerName, invoiceNumber, }, }, }, ], }, }); return runId; };
रिमाइंडर भेजने के लिए, मैं sendInvoice
सफल प्रयास के बाद scheduleReminder
कॉल करूंगा। चलिए sendEmail
trpc समापन बिंदु को संशोधित करते हैं।
// src/server/trpc/router/invoice.ts sendEmail: protectedProcedure .input(..) // omitted for brevity .mutation(async ({ input }) => { // multiplier for converting day to milliseconds. const DAY_TO_MS = 1000 * 60 * 60 * 24; // get a day before the due date const scheduledDate = new Date(input.dueDate.getTime() - DAY_TO_MS * 1); const invoiceData = {..}; //omitted for brevity await sendInvoice(invoiceData); //after the invoice is sent, schedule the reminder await scheduleReminder({ ...invoiceData, scheduledDate, }); }
अब अगर मैं ईमेल द्वारा चालान भेजने का प्रयास करता हूं, तो मुझे 20 सेकंड बाद एक रिमाइंडर मिलना चाहिए क्योंकि मैं विकास के माहौल में हूं।
अंत में, सभी सुविधाएँ तैयार हैं। हालाँकि, मुझे एक समस्या हुई, क्या होगा यदि ग्राहक ने भुगतान अनुस्मारक के लिए निर्धारित तिथि से पहले भुगतान किया हो? वर्तमान में, रिमाइंडर ईमेल अभी भी भेजा जाएगा। यह एक महान उपयोगकर्ता अनुभव और संभावित रूप से एक भ्रमित ग्राहक नहीं है। शुक्र है, कूरियर में ऑटोमेशन रद्द करने की सुविधा है।
चलिए cancelAutomationWorkflow
फ़ंक्शन जोड़ते हैं जो src/lib/courier.ts
में किसी भी ऑटोमेशन वर्कफ़्लो को रद्द कर सकता है।
export const cancelAutomationWorkflow = async ({ cancelation_token, }: { cancelation_token: string; }) => { const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { // define a cancel action, that sends a cancelation_token steps: [{ action: 'cancel', cancelation_token }], }, }); return runId; };
कैंसिलेशन_टोकन क्या है? यह एक अद्वितीय टोकन है जिसे एक ऑटोमेशन वर्कफ़्लो पर सेट किया जा सकता है, इसलिए इसे cancelation_token
के साथ cancel
कार्रवाई भेजकर रद्द किया जा सकता है।
रद्दीकरण_टोकन को scheduleReminder
में जोड़ें, मैं चालान की आईडी को टोकन के रूप में उपयोग करता हूं।
// src/lib/courier.ts export const scheduleReminder = async (..) => { // ...omitted for brevity const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { // add cancelation token here cancelation_token: `${invoiceId}-reminder`, steps: [ { action: 'delay', until: delayUntilDate.toISOString() }, // ... omitted for brevity
जब किसी इनवॉइस की स्थिति updateStatus
trpc एंडपॉइंट में PAID
के लिए अपडेट की जाती है, तो मैं cancelAutomationWorkflow
कॉल करूंगा।
// src/server/trpc/router/invoice.ts updateStatus: protectedProcedure .input(..) // omitted for brevity .mutation(async ({ ctx, input }) => { const { invoiceId, status } = input; // update an invoice's status in database const updatedInvoice = await ctx.prisma.invoice.update({ where: { id: invoiceId }, data: { status }, }); // cancel payment reminder automation workflow if the status is paid. if (updatedInvoice.status === 'PAID') { //call the cancel workflow to cancel the payment reminder for matching cancelation_token. await cancelAutomationWorkflow({ cancelation_token: `${invoiceId}-reminder`, }); } return updatedStatus; }),
यहां वर्किंग यूआई है।
नेटवर्क अनुरोध करते समय एक महत्वपूर्ण नोट विफल अनुरोधों/त्रुटियों की संभावना है। मैं इसे क्लाइंट को फेंक कर त्रुटि को संभालना चाहता हूं, इसलिए इसे यूआई में प्रतिबिंबित किया जा सकता है।
त्रुटि पर, कूरियर एपीआई डिफ़ॉल्ट रूप से CourierHttpClientError
प्रकार के साथ एक त्रुटि फेंकता है। मेरे पास नीचे दिए गए प्रारूप के अनुरूप src/lib/courier.ts
में सभी कार्यों का वापसी मूल्य भी होगा।
// On Success type SuccessResponse = { data: any, error: null } // On Error type ErrorResponse = { data: any, error: string }
अब, मैं src/lib/courier.ts
में सभी कार्यों के लिए try-catch
ब्लॉक जोड़कर त्रुटियों को संभाल सकता हूं।
try { // ..function code // modified return example return { data: runId, error: null }; } catch (error) { // make sure it's an error from Courier if (error instanceof CourierHttpClientError) { return { data: error.data, error: error.message }; } else { return { data: null, error: "Something went wrong!" }; } }
आइए sendEmail
trpc एंडपॉइंट पर एक हैंडलिंग उदाहरण देखें।
// src/server/trpc/router/invoice.ts const { error: sendError } = await sendInvoice(..); if (sendError) throw new TRPCClientError(sendError); const { error: scheduleError } = await scheduleReminder(..); if (scheduleError) throw new TRPCClientError(scheduleError);
अब जबकि सभी टेम्प्लेट तैयार हैं, मैं परीक्षण वातावरण में सभी संपत्तियों को उत्पादन में कॉपी कर दूंगा। यहाँ एक उदाहरण है।
अंत में, सभी सुविधाओं को कूरियर के साथ एकीकृत किया गया है। हम कूरियर एपीआई को नेक्स्ट.जेएस एप्लिकेशन में एकीकृत करने के कार्यप्रवाह से गुजरे हैं। हालांकि यह नेक्स्ट.जेएस और टीआरपीसी में है, कार्यप्रवाह लगभग किसी अन्य तकनीक के समान ही होगा। मुझे आशा है कि अब आप कूरियर को अपने आवेदन में स्वयं एकीकृत कर सकते हैं।
अभी शुरू करें: https://app.courier.com/signup
मैं फ़ज़्ज़ा रज़ाक अमियारसो हूँ, जो इंडोनेशिया का एक पूर्ण-स्टैक वेब डेवलपर है। मैं एक ओपन सोर्स उत्साही भी हूं। मुझे अपने ज्ञान और सीखने को अपने ब्लॉग पर साझा करना अच्छा लगता है। मैं अपने खाली समय में कभी-कभी फ्रंटेंडमेंटर पर अन्य डेवलपर्स की मदद करता हूं।
लिंक्डइन पर मेरे साथ जुड़ें।
यहाँ भी प्रकाशित हुआ।