feat(emails): implement transactional email system with Resend
Some checks failed
Build and Deploy / build (push) Has been cancelled

- Add Resend email integration with @react-email/render
- Create email templates: OrderConfirmation, OrderShipped, OrderCancelled, OrderPaid
- Implement webhook handler for ORDER_CREATED and other events
- Add multi-language support for customer emails
- Admin emails in English with order details
- Update checkout page with auto-scroll on order completion
- Configure DASHBOARD_URL environment variable
This commit is contained in:
Unchained
2026-03-25 14:13:34 +02:00
parent ef83538d0b
commit 5576946829
12 changed files with 446 additions and 96 deletions

View File

@@ -7,6 +7,7 @@ import { OrderCancelled } from "@/emails/OrderCancelled";
import { OrderPaid } from "@/emails/OrderPaid";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
const DASHBOARD_URL = process.env.DASHBOARD_URL || "https://dashboard.manoonoils.com";
interface SaleorWebhookHeaders {
"saleor-event": string;
@@ -69,6 +70,7 @@ interface SaleorOrder {
}
const SUPPORTED_EVENTS = [
"ORDER_CREATED",
"ORDER_CONFIRMED",
"ORDER_FULLY_PAID",
"ORDER_CANCELLED",
@@ -141,6 +143,7 @@ async function handleOrderConfirmed(order: SaleorOrder) {
const customerName = getCustomerName(order);
const customerEmail = order.userEmail;
const phone = order.shippingAddress?.phone || order.billingAddress?.phone;
await sendEmailToCustomer({
to: customerEmail,
@@ -168,7 +171,7 @@ async function handleOrderConfirmed(order: SaleorOrder) {
});
await sendEmailToAdmin({
subject: `New Order #${order.number} - ${customerName}`,
subject: `🎉 New Order #${order.number} - ${formatPrice(order.total.gross.amount, currency)}`,
react: OrderConfirmation({
language: "en",
orderId: order.id,
@@ -178,7 +181,11 @@ async function handleOrderConfirmed(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency),
shippingAddress: formatAddress(order.shippingAddress),
billingAddress: formatAddress(order.billingAddress),
phone,
siteUrl: SITE_URL,
dashboardUrl: DASHBOARD_URL,
isAdmin: true,
}),
eventType: "ORDER_CONFIRMED",
orderId: order.id,
@@ -360,6 +367,7 @@ async function handleSaleorWebhook(
}
switch (event) {
case "ORDER_CREATED":
case "ORDER_CONFIRMED":
await handleOrderConfirmed(order);
break;
@@ -379,6 +387,9 @@ async function handleSaleorWebhook(
export async function POST(request: NextRequest) {
try {
console.log("=== WEBHOOK RECEIVED ===");
console.log("Timestamp:", new Date().toISOString());
const body = await request.json();
const headers = request.headers;
@@ -388,6 +399,14 @@ export async function POST(request: NextRequest) {
const apiUrl = headers.get("saleor-api-url");
console.log(`Received webhook: ${event} from ${domain}`);
console.log("Headers:", { event, domain, apiUrl, hasSignature: !!signature });
console.log("Payload keys:", Object.keys(body));
if (body.order) {
console.log("Order ID:", body.order.id);
console.log("Order number:", body.order.number);
console.log("User email:", body.order.userEmail);
}
if (!event) {
return NextResponse.json({ error: "Missing saleor-event header" }, { status: 400 });