Files
saleor-core-extensions/src/lib/resend.ts
T

205 lines
6.5 KiB
TypeScript

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;
}
const FROM_EMAIL = process.env.FROM_EMAIL || "support@mail.manoonoils.com";
const FROM_NAME = process.env.FROM_NAME || "ManoonOils";
const ADMIN_EMAILS = (process.env.ADMIN_EMAILS || "me@hytham.me,tamara@hytham.me").split(",");
const SITE_URL = process.env.SITE_URL || "https://dev.manoonoils.com";
export { FROM_EMAIL, FROM_NAME, ADMIN_EMAILS, SITE_URL };
function formatPrice(amount: number, currency: string): string {
if (currency === "RSD") {
return new Intl.NumberFormat("sr-RS", {
style: "currency",
currency: "RSD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
}
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
}
function getCustomerName(order: any): string {
if (order.user?.firstName) {
return `${order.user.firstName} ${order.user.lastName || ""}`.trim();
}
if (order.billingAddress?.firstName) {
return `${order.billingAddress.firstName} ${order.billingAddress.lastName || ""}`.trim();
}
return "Customer";
}
function formatAddress(address: any): string {
if (!address) return "";
const parts = [
address.streetAddress1,
address.streetAddress2,
address.city,
address.postalCode,
address.country?.code,
].filter(Boolean);
return parts.join(", ");
}
async function sendEmail({
to,
subject,
html,
tags,
idempotencyKey,
}: {
to: string | string[];
subject: string;
html: string;
tags?: { name: string; value: string }[];
idempotencyKey?: string;
}) {
const resend = getResendClient();
const { data, error } = await resend.emails.send({
from: `${FROM_NAME} <${FROM_EMAIL}>`,
to: Array.isArray(to) ? to : [to],
subject,
html,
tags,
...(idempotencyKey && { idempotencyKey }),
});
if (error) {
console.error("Failed to send email:", error);
throw error;
}
return data;
}
export async function sendOrderConfirmationEmail(order: any) {
const customerName = getCustomerName(order);
const total = formatPrice(order.total?.gross?.amount || 0, order.total?.gross?.currency || "RSD");
const html = `
<h1>Order Confirmation #${order.number}</h1>
<p>Hello ${customerName},</p>
<p>Thank you for your order! Here are your order details:</p>
<h2>Order #${order.number}</h2>
<ul>
${order.lines?.map((line: any) => `
<li>${line.quantity}x ${line.productName} ${line.variantName ? `(${line.variantName})` : ""} - ${formatPrice(line.totalPrice?.gross?.amount || 0, line.totalPrice?.gross?.currency || "RSD")}</li>
`).join("") || ""}
</ul>
<p><strong>Total: ${total}</strong></p>
${order.shippingAddress ? `<h3>Shipping Address:</h3><p>${formatAddress(order.shippingAddress)}</p>` : ""}
<p>You can view your order details <a href="${SITE_URL}/orders/${order.id}">here</a>.</p>
<p>Thank you for shopping with us!</p>
`;
return sendEmail({
to: order.userEmail || order.user?.email || "",
subject: `Order Confirmation #${order.number}`,
html,
tags: [{ name: "type", value: "order-confirmation" }],
idempotencyKey: `order-confirmed/${order.id}`,
});
}
export async function sendOrderShippedEmail(order: any, trackingNumber?: string, trackingUrl?: string) {
const customerName = getCustomerName(order);
const html = `
<h1>Your Order #${order.number} Has Shipped!</h1>
<p>Hello ${customerName},</p>
<p>Great news! Your order has been shipped.</p>
${trackingNumber ? `<p><strong>Tracking Number:</strong> ${trackingNumber}</p>` : ""}
${trackingUrl ? `<p><a href="${trackingUrl}">Track your package</a></p>` : ""}
<p>You can view your order details <a href="${SITE_URL}/orders/${order.id}">here</a>.</p>
`;
return sendEmail({
to: order.userEmail || order.user?.email || "",
subject: `Your Order #${order.number} Has Shipped!`,
html,
tags: [{ name: "type", value: "order-shipped" }],
idempotencyKey: `order-shipped/${order.id}`,
});
}
export async function sendOrderCancelledEmail(order: any, reason?: string) {
const customerName = getCustomerName(order);
const html = `
<h1>Order #${order.number} Cancelled</h1>
<p>Hello ${customerName},</p>
<p>Your order #${order.number} has been cancelled.</p>
${reason ? `<p><strong>Reason:</strong> ${reason}</p>` : ""}
<p>If you have any questions, please contact us.</p>
`;
return sendEmail({
to: order.userEmail || order.user?.email || "",
subject: `Order #${order.number} Cancelled`,
html,
tags: [{ name: "type", value: "order-cancelled" }],
idempotencyKey: `order-cancelled/${order.id}`,
});
}
export async function sendOrderPaidEmail(order: any) {
const customerName = getCustomerName(order);
const html = `
<h1>Payment Received - Order #${order.number}</h1>
<p>Hello ${customerName},</p>
<p>We have received your payment for order #${order.number}.</p>
<p><strong>Total Paid:</strong> ${formatPrice(order.total?.gross?.amount || 0, order.total?.gross?.currency || "RSD")}</p>
<p>Thank you for your purchase!</p>
`;
return sendEmail({
to: order.userEmail || order.user?.email || "",
subject: `Payment Received - Order #${order.number}`,
html,
tags: [{ name: "type", value: "order-paid" }],
idempotencyKey: `order-paid/${order.id}`,
});
}
export async function sendAdminNotification(order: any, eventType: string) {
if (ADMIN_EMAILS.length === 0) {
console.warn("No admin emails configured");
return;
}
const total = formatPrice(order.total?.gross?.amount || 0, order.total?.gross?.currency || "RSD");
const html = `
<h1>[Admin] ${eventType} - Order #${order.number}</h1>
<p><strong>Customer:</strong> ${order.userEmail || order.user?.email || "N/A"}</p>
<p><strong>Total:</strong> ${total}</p>
<p><strong>Items:</strong> ${order.lines?.length || 0}</p>
<p><a href="${SITE_URL}/orders/${order.id}">View Order</a></p>
`;
return sendEmail({
to: ADMIN_EMAILS,
subject: `[Admin] ${eventType} - Order #${order.number}`,
html,
tags: [{ name: "type", value: "admin-notification" }],
idempotencyKey: `admin-${eventType}/${order.id}`,
});
}