หากต้องการทดสอบ Resend webhooks ภายในเครื่องใน Next.js ให้สร้างเส้นทาง App Router POST ที่อ่านเนื้อความดิบ ตรวจสอบส่วนหัว Svix ด้วยความลับในการลงนาม Resend ของคุณ และลงทะเบียน URL ช่องสัญญาณ HTTPS ในแดชบอร์ด Resend ส่งอีเมลผ่าน Resend แล้วจัดการจริง email.sent, email.delivered, email.bouncedหรือ email.complained เหตุการณ์บน localhost
เว็บฮุค Resend บอกอะไรแก่แอปพลิเคชันของคุณ
การตอบกลับของ API ที่แจ้งว่าอีเมลได้รับการยอมรับนั้นไม่ใช่ข้อพิสูจน์ว่าอีเมลถึงผู้รับแล้ว การจัดส่งเกิดขึ้นแบบอะซิงโครนัส Resend webhooks ช่วยให้แอปพลิเคชันของคุณอัปเดตสถานะข้อความ ระงับที่อยู่ที่ไม่ถูกต้อง รายงานการตีกลับ และตอบสนองต่อข้อร้องเรียนหลังจากคำขอส่งดั้งเดิมเสร็จสิ้น ที่ เอกสารประกอบเว็บฮุคอย่างเป็นทางการของ Resend แสดงรายการประเภทเหตุการณ์และการตั้งค่าแดชบอร์ด
การทดสอบ webhook ในพื้นที่ควรครอบคลุมทั้งเครื่องสถานะ ไม่ใช่แค่ว่า POST เข้าถึงเส้นทางของคุณหรือไม่เท่านั้น เชื่อมโยงรหัสอีเมลของแต่ละเหตุการณ์กับบันทึกที่สร้างขึ้นเมื่อส่ง ปฏิบัติต่อสถานะต่างๆ เสมือนเป็นการเปลี่ยนผ่าน: ยอมรับ ส่ง ส่งมอบ ล่าช้า ตีกลับ ร้องเรียน เปิด หรือคลิก ตามความเหมาะสม การทำซ้ำในภายหลังจะต้องไม่เขียนทับสถานะที่มีประโยชน์มากกว่าหรือทริกเกอร์การแจ้งเตือนเดียวกันสองครั้ง
สร้างเส้นทาง Next.js App Router
ติดตั้งตัวตรวจสอบที่ดูแลรักษาไว้สำหรับรูปแบบการลงนาม:
npm install svix
จากนั้นสร้างเส้นทาง Node-runtime Resend ลงนามที่เนื้อหาดั้งเดิม ดังนั้นจงใช้ request.text() หนึ่งครั้งก่อนที่จะแยกวิเคราะห์
// app/api/webhooks/resend/route.ts
import { Webhook } from 'svix';
export const runtime = 'nodejs';
export async function POST(request: Request) {
const payload = await request.text();
const headers = {
'svix-id': request.headers.get('svix-id') ?? '',
'svix-timestamp': request.headers.get('svix-timestamp') ?? '',
'svix-signature': request.headers.get('svix-signature') ?? '',
};
let event: ResendEvent;
try {
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET!);
event = webhook.verify(payload, headers) as ResendEvent;
} catch {
return Response.json({ error: 'Invalid webhook signature' }, { status: 400 });
}
await enqueueResendEvent({
deliveryId: headers['svix-id'],
event,
});
return Response.json({ received: true });
}
ข้อมูลลับในการลงนามเป็นของตำแหน่งข้อมูลเว็บฮุคนี้ และโดยปกติจะเริ่มต้นด้วยคำนำหน้าเฉพาะผู้ให้บริการ คัดลอกจากการตั้งค่าเว็บฮุค Resend ไปยังไฟล์สภาพแวดล้อมในเครื่องที่ถูกละเว้น เช่น .env.local. ไม่ใช่คีย์ Resend API ที่ใช้ในการส่งอีเมล
เหตุใดส่วนหัว Svix สามรายการจึงมีความสำคัญ
svix-idระบุการส่งมอบโดยไม่ซ้ำกันและเป็นคีย์ idempotency ที่ดีที่สุดsvix-timestampผูกลายเซ็นเข้ากับเวลา ทำให้ผู้ตรวจสอบสามารถปฏิเสธคำขอเก่าที่อยู่นอกขอบเขตการยอมรับได้svix-signatureสามารถมีลายเซ็นเวอร์ชันหนึ่งหรือหลายเวอร์ชันที่ใช้ในการตรวจสอบสิทธิ์เนื้อหา
อย่าใช้โปรโตคอลนี้โดยการแยกสตริงส่วนหัว เว้นแต่คุณจะมีเหตุผลที่น่าสนใจ SDK จัดการการเข้ารหัส ลายเซ็นหลายรายการ และการตรวจสอบการประทับเวลา Resend แนะนำอย่างชัดเจนให้ใช้ความลับในการลงนามและส่วนหัวเหล่านี้สำหรับการตรวจสอบ ยิ่งลึก. คู่มือการตรวจสอบลายเซ็น อธิบายว่าทำไมการตรวจสอบไบต์แบบดิบและความปลอดภัยด้านเวลาจึงมีความสำคัญ
เปิดเผย Next.js และลงทะเบียนจุดสิ้นสุด
- วิ่ง
npm run devและยืนยันว่าแอปพลิเคชันฟังบนพอร์ต 3000 - เริ่ม
npx portpreview 3000ในเทอร์มินัลอื่น - ใน Resend ให้สร้างเว็บฮุคที่มีจุดสิ้นสุด
https://YOUR-TUNNEL.portpreview.dev/api/webhooks/resend. - เลือกเฉพาะกิจกรรมอีเมลที่แอปพลิเคชันของคุณดำเนินการ
- คัดลอกความลับในการลงนามของปลายทางไปไว้
RESEND_WEBHOOK_SECRETและรีสตาร์ท Next.js เพื่อให้โหลดตัวแปร - ส่งข้อความโดยใช้โดเมนที่ได้รับการยืนยันและตรวจสอบเหตุการณ์ที่เข้าถึงเส้นทางท้องถิ่น
รักษา URL สาธารณะให้คงที่สำหรับเซสชัน หากต้นทางของทันเนลเปลี่ยนแปลง ให้แก้ไขตำแหน่งข้อมูล Resend ก่อนทำการทดสอบอีกครั้ง อุปกรณ์ปลายทางที่กำหนดค่าด้วย URL เก่าไม่สามารถเข้าถึงกระบวนการใหม่ของคุณได้ แม้ว่า localhost จะทำงานได้ดีก็ตาม
ใช้โปรแกรมเลือกจ่ายงานที่พิมพ์
เพย์โหลดของ Webhook ควรป้อนโปรแกรมเลือกจ่ายงานที่แคบเพียงรายการเดียว ตรวจสอบความถูกต้องของช่องที่ต้องกรอกและทำให้ประเภทเหตุการณ์ที่ไม่รู้จักสามารถสังเกตได้ โดยไม่ถือเป็นความล้มเหลวของเซิร์ฟเวอร์
async function processEvent(event: ResendEvent) {
switch (event.type) {
case 'email.delivered':
await markDelivered(event.data.email_id, event.created_at);
break;
case 'email.bounced':
await markBounced(event.data.email_id, event.data.bounce?.message);
await suppressIfPermanent(event.data);
break;
case 'email.complained':
await suppressRecipients(event.data.to);
await alertCompliance(event.data.email_id);
break;
default:
await recordUnhandledEvent(event);
}
}
รักษาประเภทเพย์โหลดให้สอดคล้องกับสคีมา Resend ปัจจุบัน แทนที่จะถือว่าทุกเหตุการณ์มีข้อมูลที่เหมือนกัน ตัวอย่างเช่น รายละเอียดการตีกลับและรายชื่อผู้รับอาจเกี่ยวข้องกับบางกิจกรรมเท่านั้น บันทึกประเภทเหตุการณ์ รหัสอีเมลของผู้ให้บริการ การประทับเวลาของเหตุการณ์ และเพย์โหลดที่แก้ไขขั้นต่ำสำหรับการตรวจสอบการสนับสนุน
ทำให้การจัดการ idempotent ก่อนที่จะลองทดสอบอีกครั้ง
ระบบ Webhook จัดให้มีพฤติกรรมการจัดส่งอย่างน้อยหนึ่งครั้งในทางปฏิบัติ การหมดเวลาอาจเกิดขึ้นได้หลังจากที่ฐานข้อมูลของคุณคอมมิต แต่ก่อนที่ผู้ให้บริการจะได้รับการตอบกลับ 200 ครั้งของคุณ ผู้ให้บริการจึงลองส่งคำขอที่คุณสมัครไปแล้วอีกครั้ง ใช้ svix-id เป็นคีย์การจัดส่งที่ไม่ซ้ำกันและแทรกลงในธุรกรรมเดียวกันกับสถานะที่เปลี่ยนแปลง
await db.transaction(async (tx) => {
const inserted = await tx.webhookDelivery.insertOnce({
provider: 'resend',
deliveryId,
});
if (!inserted) return;
await applyEmailEvent(tx, event);
});
อย่ากรองข้อมูลที่ซ้ำกันโดยใช้รหัสอีเมลเพียงอย่างเดียว เนื่องจากอีเมลหนึ่งได้รับกิจกรรมหลายประเภทอย่างถูกต้องตามกฎหมาย ขึ้นอยู่กับโมเดลข้อมูลของคุณ ให้เก็บทั้งคีย์เฉพาะระดับการจัดส่งและกฎการเปลี่ยนสถานะ อ่าน รูปแบบการลองซ้ำของ webhook และ idempotency ก่อนที่จะเชื่อมต่อเหตุการณ์กับการเรียกเก็บเงิน การระงับ หรือการแจ้งเตือนลูกค้า
กลับอย่างรวดเร็วไม่แพ้กิจกรรม
การตรวจสอบลายเซ็นมีความเหมาะสมในเส้นทางคำขอ งานธุรกิจที่ช้าไม่ได้ คงอยู่หรือเข้าคิวเหตุการณ์ที่ยืนยันแล้ว จากนั้นส่งคืน 2xx หากคุณกลับมาก่อนที่จะเขียนแบบถาวร กระบวนการขัดข้องอาจสูญเสียเหตุการณ์ได้ หากคุณรอ API ระยะไกลหลายรายการ ตำแหน่งข้อมูลของคุณอาจหมดเวลาและเชิญให้ลองใหม่ ตารางกล่องจดหมายของฐานข้อมูลมักเป็นการออกแบบในท้องถิ่นและการใช้งานจริงที่ง่ายที่สุด
สร้างกิจกรรมการทดสอบที่เป็นประโยชน์
ส่งแล้วคับ
ส่งไปยังที่อยู่ที่คุณควบคุมจากโดเมนที่ได้รับการยืนยัน บันทึก ID อีเมลที่ส่งคืนโดย send API และยืนยันว่ากิจกรรมขาเข้าอัปเดตแถวเดียวกัน เวลาในการจัดส่งจะแตกต่างกันไปตามเซิร์ฟเวอร์ของผู้รับ ดังนั้นอย่าถือว่าเหตุการณ์มาถึงทันทีหรือเป็นลำดับแบบง่ายๆ
ตีกลับ
ใช้ที่อยู่ทดสอบหรือคุณลักษณะการทดสอบที่บันทึกไว้ของ Resend แทนที่จะสร้างการรับส่งข้อมูลไปยังโดเมนที่ไม่เกี่ยวข้อง ตรวจสอบว่าความล้มเหลวถาวรระงับเมลในอนาคตในขณะที่เงื่อนไขชั่วคราวเป็นไปตามนโยบายการลองใหม่ของคุณ อย่าระงับทุกเหตุการณ์ที่ล่าช้าโดยอัตโนมัติ
การร้องเรียน
การจัดการข้อร้องเรียนเป็นทั้งความสามารถในการส่งมอบและตรรกะในการปฏิบัติตามข้อกำหนด ตรวจสอบให้แน่ใจว่าเว็บฮุคที่ซ้ำกันไม่สร้างการแจ้งเตือนซ้ำ และให้แน่ใจว่าผู้รับที่ได้รับผลกระทบถูกแยกออกจากแคมเปญในภายหลังตามนโยบายของคุณ
แก้ไขปัญหาความล้มเหลวของเว็บฮุค Resend
การตรวจสอบลายเซ็นล้มเหลวเสมอ
ยืนยันว่ามีการโหลดความลับในการเซ็นชื่อปลายทาง ไม่ใช่คีย์ API แล้ว ใช้ await request.text()อย่าแยกวิเคราะห์และสตริง JSON ใหม่ และส่งผ่านส่วนหัว Svix ทั้งสามรายการด้วยค่าที่ตรงกันทั้งหมด รีสตาร์ทเซิร์ฟเวอร์การพัฒนาหลังจากการเปลี่ยนแปลง .env.local.
เส้นทางส่งคืน 404 หรือ 405
ต้องตั้งชื่อไฟล์เส้นทาง App Router route.ts ใต้ส่วน URL ที่ต้องการและส่งออก POST. ตรวจสอบว่ามิดเดิลแวร์เขียนคำขอช่องสัญญาณใหม่ไปยังสถานที่หรือหน้าเข้าสู่ระบบหรือไม่ ทดสอบ URL สาธารณะด้วย curl และตรวจสอบการตอบสนองจริง
Resend แสดงการลองใหม่แม้ว่าการประมวลผลจะสำเร็จก็ตาม
ตรวจสอบว่าทุกสาขาที่ประสบความสำเร็จส่งคืน 2xx ทันที ข้อผิดพลาดที่เกิดขึ้นหลังจากการอัพเดตฐานข้อมูลสามารถสร้าง 500 และลองซ้ำอีกครั้ง ทำการประมวลผลแบบทรานแซคชันและแบบ idempotent จากนั้นตรวจสอบเวลาแฝงในการตอบสนอง
กิจกรรมมาถึงแต่ไม่สามารถเชื่อมโยงกับอีเมลได้
คงรหัสอีเมลของผู้ให้บริการจาก Resend ดั้งเดิมที่ส่งตอบกลับ อย่าใช้หัวเรื่องหรือที่อยู่ผู้รับเป็นตัวระบุ ฟิลด์เหล่านั้นไม่ซ้ำกันและไม่เสถียรเพียงพอสำหรับความสัมพันธ์
การบันทึกที่เล่นซ้ำไม่ผ่านการตรวจสอบการประทับเวลา
นั่นคือสิ่งที่คาดหวังเมื่อเล่นซ้ำคำขอที่ลงนามเก่าผ่านตัวตรวจสอบปกติ: การประทับเวลาอาจอยู่นอกเกณฑ์ความคลาดเคลื่อนที่อนุญาต ต้องการผู้ให้บริการจัดส่งใหม่หากมี สำหรับการทดสอบตรรกะทางธุรกิจแบบแยกส่วน ให้ตรวจสอบเพียงครั้งเดียว บันทึกโปรแกรมกิจกรรมที่ผ่านการฆ่าเชื้อ และทดสอบผู้มอบหมายงานแยกกัน ที่ คู่มือการเล่นซ้ำ อธิบายขอบเขตนี้
ความปลอดภัยและความเป็นส่วนตัวสำหรับการทดสอบกิจกรรมอีเมล
- อย่าเปิดเผย
RESEND_API_KEYหรือความลับในการลงนามปลายทางในแหล่งที่มา ชุดเบราว์เซอร์ ภาพหน้าจอ หรือบันทึกคำขอ - ตรวจสอบก่อนแยกวิเคราะห์หรือคงเหตุการณ์ไว้
- ตรวจทานผู้รับ หัวข้อ ส่วนหัว และข้อมูลเมตาของข้อความจากการบันทึกช่องสัญญาณที่ใช้ร่วมกัน
- ใช้ขีดจำกัดการเก็บรักษากับเพย์โหลดดิบของ Webhook จัดเก็บเฉพาะสิ่งที่ต้องการการสนับสนุนและการปฏิบัติตามข้อกำหนดเท่านั้น
- ใช้ข้อมูลลับปลายทางในเครื่องแยกต่างหากจากการใช้งานจริง และหมุนเวียนข้อมูลดังกล่าวเมื่อลบจุดสิ้นสุดการทดสอบ
การออกแบบขั้นสุดท้ายควรทำงานเหมือนกันหลังจากการปรับใช้: ตำแหน่งข้อมูล HTTPS สาธารณะ, การตรวจสอบ Raw-Body, ค่าเดิมที่คงทน, การรับทราบอย่างรวดเร็ว และการจัดการสถานะแบบอะซิงโครนัส สำหรับรายละเอียด Raw Body เฉพาะของ App Router โปรดดูที่ Next.js คู่มือเว็บฮุคโลคัลโฮสต์.
