paint-brush
Courier API を使用して Next.js に請求書を送信し、支払い通知を追加する@courier
1,495 測定値
1,495 測定値

Courier API を使用して Next.js に請求書を送信し、支払い通知を追加する

Courier14m2023/02/14
Read on Terminal Reader

長すぎる; 読むには

多くのオープンソースの請求書管理アプリが Laravel で構築されています。 React と Javascript に精通している開発者向けの「React ソリューション」を構築したかったのです。 Node.js でサービスを構築するときに見つけた問題は、メーラーが組み込まれていないことです。そのため、それを行うサードパーティのサービスを見つける必要がありました。この記事では、[Courier] を統合してこのプロジェクトのメールを送信します。
featured image - Courier API を使用して Next.js に請求書を送信し、支払い通知を追加する
Courier HackerNoon profile picture
0-item

多くのオープンソースの請求書管理アプリが Laravel で構築されています。 Javascript 開発者として、React と Javascript に精通している開発者向けの「React ソリューション」を構築したいと考えていました。


Node.js でサービスを構築するときに私が見つけた問題は、メーラーが組み込まれていないことです。そのため、それを行うサードパーティのサービスを見つける必要がありました。この記事では、 Courierを統合して、このプロジェクトhttps://github.com/fazzaamiarso/invoysのメールを送信します。

前提条件

この記事は一般的なフォロー アロングではないため (「じっと座って、私のやり方を見てください」というようなものです)、使用されているすべてのテクノロジに精通している必要はありません。ただし、Typescript と Next.js に精通していると、より迅速に理解できるようになります。


このブログのテクノロジー:


  • Typescript : 型安全性とオートコンプリートが最適ですよね?

  • Next.js : 初心者でもフルスタック アプリを構築するための本番環境対応フレームワーク。

  • Prisma : データベースを操作するための優れた ORM。型安全性とオートコンプリートのために Prisma を使用しており、typescript が追加された優れた開発者エクスペリエンスを提供します。

  • Trpc : Next.js クライアントとサーバーの間でエンドツーエンドの型安全性を簡単に構築できるようにします。

  • Courier API: メール、SMS などの通知を処理するための優れたサービス/プラットフォームです。


完全なソース コードは、参照用にここにあります。

目標

機能を構築する前に、目標を定義しましょう。


  1. クライアントの電子メールに請求書のリンクを送信します。
  2. 請求書の期日の前日にリマインダーを送信します。
  3. 請求書がすでに支払われている場合、請求書の期日リマインダーをキャンセルします。
  4. ネットワーク エラーの処理。

パート 1: Courier プラットフォームのセットアップ

Courier ダッシュボードに行きましょう。デフォルトでは、本番環境にあります。テストしたいので、右上隅のドロップダウンをクリックしてテスト環境に変更します。


後ですべてのテンプレートを本番環境にコピーしたり、その逆にコピーしたりできます。


次に、メール通知用のブランドを作成します。



ヘッダーにロゴを追加し (ロゴの幅は 140px に固定されていることに注意してください)、フッターにソーシャル リンクを追加します。デザイナーの UI は非常に単純なので、最終的な結果は次のとおりです。



変更を公開することを忘れないでください。

パート 2: 請求書を電子メールで送信する

現在、UI のメール送信ボタンは何もしていません。


Courier 関連のすべてのコードを保持するためにsrc/lib/courier.tsファイルを作成します。また、すべての Courier API エンドポイントを関数に抽象化したcourier node.js クライアント ライブラリを使用します。


機能を構築する前に、Courier's Designer 内で電子メール通知デザインを作成し、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); }),


trpc に慣れていない人のために、私がやったことはPOSTリクエストの処理と同じです。分解してみましょう。


  1. 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(), }) )
  1. 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); }),

フロントエンド

これで、メール送信ボタンに機能を追加できるようになりました。 tanstack-query's useMutation` の薄いラッパーであるtrpc.useMutation()関数を使用します。


ミューテーション機能を追加しましょう。応答が成功したら、UI で成功のトーストを送信したいと思います。

 //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です。

パート 3: 支払い通知を送信する

請求書の期日の前日に送信されるリマインダーをスケジュールするには、 Courier の Automation APIを使用します。


まず、Courier デザイナーでメール テンプレートをデザインしましょう。すでにプロセスを経ているので、これが最終的な結果です。


関数を追加する前に、パラメーターの型を定義し、型をリファクタリングします。

 // 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/courierscheduleReminder関数を追加します。

 //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 秒後にリマインダーを受け取るはずです。

パート 4: リマインダーをキャンセルする

最後に、すべての機能の準備が整いました。しかし、問題が発生しました。クライアントが支払督促の予定日よりも前に支払った場合はどうなりますか?現在、リマインダー メールは引き続き送信されます。これは優れたユーザー エクスペリエンスではなく、クライアントが混乱する可能性があります。ありがたいことに、Courier には自動キャンセル機能があります。


src/lib/courier.tsに任意の自動化ワークフローをキャンセルできるcancelAutomationWorkflow関数を追加しましょう。

 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 とは何ですか?自動化ワークフローに設定できる一意のトークンであるため、対応するcancelation_tokencancelアクションを送信することでキャンセルできます。


scheduleReminderに cancelation_token を追加し、請求書の Id をトークンとして使用します。

 // 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; }),

これが作業UIです。

パート 5: エラー処理

ネットワーク リクエストを行う際の重要な注意点は、失敗したリクエストやエラーが発生する可能性があることです。 UIに反映できるように、エラーをクライアントに投げて処理したい。


エラーが発生すると、Courier API はデフォルトで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);

パート 6: 本番環境への移行

すべてのテンプレートの準備が整ったので、テスト環境のすべてのアセットを本番環境にコピーします。ここに例があります。

結論

最後に、すべての機能が Courier に統合されています。 Courier API を Next.js アプリケーションに統合するワークフローを確認しました。 Next.js と trpc にありますが、ワークフローは他のテクノロジとほとんど同じです。 Courier を自分でアプリケーションに統合できるようになったことを願っています。


今すぐ始めましょう: https://app.courier.com/signup

著者について

私は、インドネシアのフルスタック Web 開発者である Fazza Razaq Amiarso です。私はオープンソースの愛好家でもあります。ブログで自分の知識と学習を共有するのが大好きです。私は時々、空き時間にFrontendMentorで他の開発者を支援しています。


LinkedInで私とつながりましょう。

クイックリンク

🔗クーリエドキュメント

🔗インボイに貢献する

🔗インボイのモチベーション


ここにも掲載されています。