feat: Add Saleor webhook handler with Resend email integration
- Add Resend SDK for transactional emails - Create React Email templates for order events: - OrderConfirmation - OrderShipped - OrderCancelled - OrderPaid - Multi-language support (SR, EN, DE, FR) - Customer emails in their language - Admin emails in English to me@hytham.me and tamara@hytham.me - Webhook handler at /api/webhooks/saleor - Supports: ORDER_CONFIRMED, ORDER_FULLY_PAID, ORDER_CANCELLED, ORDER_FULFILLED - Add GraphQL mutation to create webhooks in Saleor - Add Resend API key to .env.local
This commit is contained in:
100
src/lib/resend.ts
Normal file
100
src/lib/resend.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Resend } from "resend";
|
||||
|
||||
let resendClient: Resend | null = null;
|
||||
|
||||
function getResendClient(): Resend {
|
||||
if (!resendClient) {
|
||||
if (!process.env.RESEND_API_KEY) {
|
||||
throw new Error("RESEND_API_KEY environment variable is not set");
|
||||
}
|
||||
resendClient = new Resend(process.env.RESEND_API_KEY);
|
||||
}
|
||||
return resendClient;
|
||||
}
|
||||
|
||||
export const ADMIN_EMAILS = ["me@hytham.me", "tamara@hytham.me"];
|
||||
|
||||
export async function sendEmail({
|
||||
to,
|
||||
subject,
|
||||
react,
|
||||
text,
|
||||
tags,
|
||||
idempotencyKey,
|
||||
}: {
|
||||
to: string | string[];
|
||||
subject: string;
|
||||
react: React.ReactNode;
|
||||
text?: string;
|
||||
tags?: { name: string; value: string }[];
|
||||
idempotencyKey?: string;
|
||||
}) {
|
||||
const resend = getResendClient();
|
||||
const { data, error } = await resend.emails.send(
|
||||
{
|
||||
from: "ManoonOils <support@manoonoils.com>",
|
||||
to: Array.isArray(to) ? to : [to],
|
||||
subject,
|
||||
react,
|
||||
text,
|
||||
tags,
|
||||
...(idempotencyKey && { idempotencyKey }),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
console.error("Failed to send email:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function sendEmailToCustomer({
|
||||
to,
|
||||
subject,
|
||||
react,
|
||||
text,
|
||||
language,
|
||||
idempotencyKey,
|
||||
}: {
|
||||
to: string;
|
||||
subject: string;
|
||||
react: React.ReactNode;
|
||||
text?: string;
|
||||
language: string;
|
||||
idempotencyKey?: string;
|
||||
}) {
|
||||
const tag = `customer-${language}`;
|
||||
return sendEmail({
|
||||
to,
|
||||
subject,
|
||||
react,
|
||||
text,
|
||||
tags: [{ name: "type", value: tag }],
|
||||
idempotencyKey,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendEmailToAdmin({
|
||||
subject,
|
||||
react,
|
||||
text,
|
||||
eventType,
|
||||
orderId,
|
||||
}: {
|
||||
subject: string;
|
||||
react: React.ReactNode;
|
||||
text?: string;
|
||||
eventType: string;
|
||||
orderId: string;
|
||||
}) {
|
||||
return sendEmail({
|
||||
to: ADMIN_EMAILS,
|
||||
subject: `[Admin] ${subject}`,
|
||||
react,
|
||||
text,
|
||||
tags: [{ name: "type", value: "admin-notification" }],
|
||||
idempotencyKey: `admin-${eventType}/${orderId}`,
|
||||
});
|
||||
}
|
||||
77
src/lib/saleor/create-webhooks.graphql
Normal file
77
src/lib/saleor/create-webhooks.graphql
Normal file
@@ -0,0 +1,77 @@
|
||||
mutation CreateSaleorWebhooks {
|
||||
orderConfirmedWebhook: webhookCreate(input: {
|
||||
name: "Resend - Order Confirmed"
|
||||
targetUrl: "https://manoonoils.com/api/webhooks/saleor"
|
||||
events: [ORDER_CONFIRMED]
|
||||
isActive: true
|
||||
}) {
|
||||
webhook {
|
||||
id
|
||||
name
|
||||
targetUrl
|
||||
isActive
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
code
|
||||
}
|
||||
}
|
||||
|
||||
orderPaidWebhook: webhookCreate(input: {
|
||||
name: "Resend - Order Paid"
|
||||
targetUrl: "https://manoonoils.com/api/webhooks/saleor"
|
||||
events: [ORDER_FULLY_PAID]
|
||||
isActive: true
|
||||
}) {
|
||||
webhook {
|
||||
id
|
||||
name
|
||||
targetUrl
|
||||
isActive
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
code
|
||||
}
|
||||
}
|
||||
|
||||
orderCancelledWebhook: webhookCreate(input: {
|
||||
name: "Resend - Order Cancelled"
|
||||
targetUrl: "https://manoonoils.com/api/webhooks/saleor"
|
||||
events: [ORDER_CANCELLED]
|
||||
isActive: true
|
||||
}) {
|
||||
webhook {
|
||||
id
|
||||
name
|
||||
targetUrl
|
||||
isActive
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
code
|
||||
}
|
||||
}
|
||||
|
||||
orderFulfilledWebhook: webhookCreate(input: {
|
||||
name: "Resend - Order Fulfilled"
|
||||
targetUrl: "https://manoonoils.com/api/webhooks/saleor"
|
||||
events: [ORDER_FULFILLED]
|
||||
isActive: true
|
||||
}) {
|
||||
webhook {
|
||||
id
|
||||
name
|
||||
targetUrl
|
||||
isActive
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user