iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Vibe Coding

Vibe Mode 開啟:30 天用 AI 打造網頁,邊做邊學 JavaScript系列 第 19 篇

# Day 19:接入第三方服務:支付系統 (Stripe) 或 Email 發送 (Resend) 串接

  • 分享至 

  • xImage
  •  

歡迎來到階段四:進階功能與 AI 功能整合!在完成階段三前端體驗的極致雕琢後,今天我們要為我們的 「AI 智慧個人財務追蹤器 (Smart Finance Tracker)」 賦予商業化的靈魂。
我們將整合現代開發者最愛的 Resend 電子郵件服務,實現「每週財務週報」與「異常消費警示」自動寄送功能!

今日目標

在 Next.js App Router 中整合 Resend SDK 與 React Email。

讓 AI 生成具備響應式設計且視覺精美的 HTML Email 範本。

掌握在 Vibe Coding 下如何引導 AI 處理 Webhook、API Key 安全防護以及 Resend 的非同步寄送狀態。

Human vs. AI:提示詞與協同開發

使用傳統方法寫 HTML Email 簡直是前端工程師的噩夢(要用 刻排版、處理各大 Email Client 的樣式相容性)。現在利用 React Email + Resend 搭配 AI,幾分鐘就能產出極具現代感的郵件範本!

給 AI 的指令(Prompt)

我們正在 Next.js (App Router) 專案中整合 Resend 與 React Email。
請幫我建立一個週報郵件元件 src/emails/WeeklyReportEmail.tsx 與對應的 API Route src/app/api/send-report/route.ts。

要求:

  1. 郵件範本 WeeklyReportEmail 使用 @react-email/components 撰寫,接收 userName, totalExpense, topCategory 三個 Props。
  2. 視覺風格需採用 Dark Mode 現代科技感(背景 #0f172a,搭配紫色與綠色強調色)。
  3. API Route 使用 resend SDK 發送郵件,並從 .env.local 讀取 RESEND_API_KEY。
  4. 包含完善的 Try-Catch 錯誤處理與 Zod 驗證 Request Body。

AI 產出的原始結果

// src/app/api/send-report/route.ts
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import { WeeklyReportEmail } from '@/emails/WeeklyReportEmail';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: Request) {
  try {
    const { email, userName, totalExpense, topCategory } = await req.json();

    const data = await resend.emails.send({
      from: 'Finance Tracker <onboarding@resend.dev>',
      to: email,
      subject: '您的本週財務週報已送達!',
      react: WeeklyReportEmail({ userName, totalExpense, topCategory }),
    });

    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

翻車與除錯過程 (Debug Experience)

這段 Code 在本機測試時,一發送 API 請求,控制台立刻噴出連串錯誤,郵件完全寄不出去:

Error: React.createElement: type is invalid -- expected a string or a class/function but got:

哪裡有問題?

  1. React Email JSX 傳參語法錯誤:
    在 Resend SDK 中,react: 屬性應該接收一個 JSX 元素(即 <WeeklyReportEmail userName={userName} ... />),但 AI 卻將它寫成了函式呼叫 WeeklyReportEmail({...})。這直接打破了 React 的渲染機制!

  2. 缺少 API Key 未設定的防禦性檢查:
    如果 .env.local 沒有設定 RESEND_API_KEY,Resend SDK 在初始化或呼叫時會拋出非常晦澀的內部錯誤,AI 產出的程式碼完全沒有在最上層做環境變數檢查。

  3. 預設寄件者網域限制(Domain Sandbox):
    Resend 測試階段預設只能寄給「帳號註冊者自己的 Email」,如果發送給任意第三方測試信箱,Resend 會回傳 403 Forbidden。AI 沒有在語法中捕獲此特定錯誤提示,導致前端只拿到模糊的 500 Internal Server Error。

修正與引導(Human Decision)

我整理了錯誤訊息,並向 Cursor 下達修復指令:

修正 Prompt:
「1. resend.emails.send 中的 react 欄位請改為正確的 JSX 語法 <WeeklyReportEmail ... />。
2. 請在 route.ts 頂部加入 process.env.RESEND_API_KEY 的檢查,若缺失則回傳 500 告知環境變數未設定。
3. 請使用 Zod 驗證 Request Body,並處理 Resend API 回傳的錯誤物件(Resend 的 error 不會觸發 Catch,需手動檢查 data.error)。」

修正後的版本 (Refactored Code)

經過調整後,我們獲得了一套具備生產環境強度(Production-ready)的 API 與郵件寄送邏輯:


// src/app/api/send-report/route.ts
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import { z } from 'zod';
import WeeklyReportEmail from '@/emails/WeeklyReportEmail';

// 1. Zod 嚴格驗證輸入欄位
const sendReportSchema = z.object({
  email: z.string().email('無效的 Email 格式'),
  userName: z.string().min(1, '用戶姓名不能為空'),
  totalExpense: z.number().nonnegative(),
  topCategory: z.string(),
});

export async function POST(req: Request) {
  // 2. 環境變數檢查
  if (!process.env.RESEND_API_KEY) {
    return NextResponse.json({ error: '伺服器未設定 RESEND_API_KEY' }, { status: 500 });
  }

  const resend = new Resend(process.env.RESEND_API_KEY);

  try {
    const body = await req.json();
    const validationResult = sendReportSchema.safeParse(body);

    if (!validationResult.success) {
      return NextResponse.json({ error: validationResult.error.flatten() }, { status: 400 });
    }

    const { email, userName, totalExpense, topCategory } = validationResult.data;

    // 3. 正確使用 JSX 語法傳遞 React Email 組件
    const { data, error } = await resend.emails.send({
      from: 'Smart Finance <weekly@yourdomain.com>',
      to: [email],
      subject: '您的本週財務週報已送達!',
      react: <WeeklyReportEmail userName={userName} totalExpense={totalExpense} topCategory={topCategory} />,
    });

    // 4. Resend 的 API 錯誤處理(Resend 不會直接 throw error,而是回傳 error 物件)
    if (error) {
      console.error('Resend API Error:', error);
      return NextResponse.json({ error: error.message }, { status: 400 });
    }

    return NextResponse.json({ success: true, messageId: data?.id });
  } catch (error: any) {
    console.error('Server Unhandled Error:', error);
    return NextResponse.json({ error: error.message || '伺服器內部錯誤' }, { status: 500 });
  }
}

JavaScript 筆記

SDK 錯誤處理的兩大派系(Throw vs. Return Object):
傳統 SDK(如 Stripe、OpenAI)在發生錯誤時通常會直接 throw new Error(),需要用 try-catch 捕獲;但許多現代輕量化 SDK(如 Resend、Supabase)則偏好回傳 { data, error } 物件。AI 常常會混淆這兩種模式,開發者必須保有明確的判斷能力。

React Email 與 Server Component 的交集:
React Email 的妙處在於能讓我們用撰寫 React UI 的方式寫 Email。但在 Server 端渲染 Email JSX 時,要確保傳入的是編譯後的合法 React Node,否則會破壞 SSR/Node 的模組解析。

小結

今天我們成功把 Resend 電子郵件服務整合進專案中,讓我們的財務追蹤器具備了與使用者保持互動的通知機制!
明日 Day 20,我們將進入本階段的重頭戲 —— 在 Web 中整合 LLM API(OpenAI / Anthropic SDK),正式開啟專案的「AI 智慧分析」核心功能!敬請期待!


上一篇
# Day 18 - 前端 E2E 測試:AI 協助撰寫 Playwright 自動化測試腳步
下一篇
# Day 20 :在 Web 中整合 LLM API:使用 OpenAI / Anthropic SDK 實現產品核心 AI 功能
系列文
Vibe Mode 開啟:30 天用 AI 打造網頁,邊做邊學 JavaScript 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言