205 lines
6.5 KiB
TypeScript
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}`,
|
|
});
|
|
} |