import { components } from "./_generated/api"; import { Resend } from "@convex-dev/resend"; import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; // ─── Component instance ─────────────────────────────────────────────────────── export const resend = new Resend(components.resend, { // Set testMode: false once you have a verified Resend domain and want to // deliver to real addresses. While testMode is true, only Resend's own test // addresses (e.g. delivered@resend.dev) will actually receive mail. testMode: false, }); // Update this once your sending domain is verified in Resend. const FROM = "The Pet Loft "; // ─── HTML helpers ───────────────────────────────────────────────────────────── function base(body: string): string { return `

The Pet Loft

${body}

© ${new Date().getFullYear()} The Pet Loft. All rights reserved.

`; } function formatPrice(amountInSmallestUnit: number, currency: string): string { return new Intl.NumberFormat("en-GB", { style: "currency", currency: currency.toUpperCase(), }).format(amountInSmallestUnit / 100); } function btn(href: string, label: string): string { return `${label}`; } // ─── Order confirmation ─────────────────────────────────────────────────────── export const sendOrderConfirmation = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), total: v.number(), currency: v.string(), items: v.array( v.object({ productName: v.string(), variantName: v.string(), quantity: v.number(), unitPrice: v.number(), }), ), shippingAddress: v.object({ fullName: v.string(), addressLine1: v.string(), city: v.string(), postalCode: v.string(), country: v.string(), }), }, handler: async (ctx, args) => { const rows = args.items .map( (item) => ` ${item.productName}
${item.variantName} ×${item.quantity} ${formatPrice(item.unitPrice * item.quantity, args.currency)} `, ) .join(""); const addr = args.shippingAddress; const html = base(`

Order confirmed!

Hi ${args.firstName}, thank you for your order. We’re getting it ready now.

Order: ${args.orderNumber}

${rows}
Total ${formatPrice(args.total, args.currency)}

Shipping to:

${addr.fullName}
${addr.addressLine1}
${addr.city}, ${addr.postalCode}
${addr.country}

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Order confirmed — ${args.orderNumber}`, html, }); }, }); // ─── Shipping confirmation ──────────────────────────────────────────────────── export const sendShippingConfirmation = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), trackingNumber: v.string(), trackingUrl: v.string(), carrier: v.string(), estimatedDelivery: v.optional(v.number()), }, handler: async (ctx, args) => { const eta = args.estimatedDelivery ? `

Estimated delivery: ${new Date(args.estimatedDelivery).toDateString()}

` : ""; const html = base(`

Your order is on its way!

Hi ${args.firstName}, ${args.orderNumber} has been shipped.

Carrier: ${args.carrier}

Tracking number: ${args.trackingNumber}

${eta} ${btn(args.trackingUrl, "Track your order")} `); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Your order ${args.orderNumber} has shipped`, html, }); }, }); // ─── Delivery confirmation ──────────────────────────────────────────────────── export const sendDeliveryConfirmation = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), }, handler: async (ctx, args) => { const html = base(`

Your order has been delivered!

Hi ${args.firstName}, your order ${args.orderNumber} has been delivered.

We hope your pets love their new goodies! If anything is wrong with your order, please contact our support team.

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Your order ${args.orderNumber} has been delivered`, html, }); }, }); // ─── Cancellation ───────────────────────────────────────────────────────────── export const sendCancellationNotice = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), }, handler: async (ctx, args) => { const html = base(`

Order cancelled

Hi ${args.firstName}, your order ${args.orderNumber} has been cancelled.

If you did not request this cancellation or need help, please get in touch with our support team.

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Your order ${args.orderNumber} has been cancelled`, html, }); }, }); // ─── Refund ─────────────────────────────────────────────────────────────────── export const sendRefundNotice = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), total: v.number(), currency: v.string(), }, handler: async (ctx, args) => { const html = base(`

Refund processed

Hi ${args.firstName}, your refund for order ${args.orderNumber} has been processed.

Refund amount: ${formatPrice(args.total, args.currency)}

Please allow 5–10 business days for the amount to appear on your statement.

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Refund processed for order ${args.orderNumber}`, html, }); }, }); // ─── Return label ───────────────────────────────────────────────────────────── export const sendReturnLabelEmail = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), returnLabelUrl: v.string(), }, handler: async (ctx, args) => { const html = base(`

Your return label is ready

Hi ${args.firstName}, your return request for order ${args.orderNumber} has been accepted.

Please use the link below to download your prepaid return label and attach it to your parcel.

${btn(args.returnLabelUrl, "Download return label")}

Once we receive your return, we’ll process your refund promptly.

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Return label for order ${args.orderNumber}`, html, }); }, }); // ─── Return requested ───────────────────────────────────────────────────────── export const sendReturnRequestedNotice = internalMutation({ args: { to: v.string(), firstName: v.string(), orderNumber: v.string(), }, handler: async (ctx, args) => { const html = base(`

Return request received

Hi ${args.firstName}, we’ve received your return request for order ${args.orderNumber}.

Our team will review it and get back to you within 2 business days.

`); await resend.sendEmail(ctx, { from: FROM, to: args.to, subject: `Return request received for order ${args.orderNumber}`, html, }); }, });